@kybernesis/payments 0.1.0 → 0.3.0
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 +5 -6
- package/dist/approve-ask.d.ts +50 -0
- package/dist/approve-ask.js +42 -0
- package/dist/approve-steps.d.ts +8 -0
- package/dist/approve-steps.js +39 -0
- package/dist/approve.d.ts +55 -0
- package/dist/approve.js +79 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/instructions.d.ts +1 -1
- package/dist/instructions.js +3 -3
- package/dist/link-tools.d.ts +29 -0
- package/dist/link-tools.js +71 -0
- package/dist/pay-on-computer.d.ts +3 -3
- package/dist/pay-on-computer.js +2 -5
- package/package.json +13 -4
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@ friends: the agent names a merchant and a total, the person approves it in the
|
|
|
7
7
|
Link app, and Link issues a one-time card for exactly that purchase. This
|
|
8
8
|
package adds the two pieces that make it usable by a Kybernesis agent:
|
|
9
9
|
|
|
10
|
+
- **`linkTool(name)`** — Link's tool catalog (`@stripe/link-sdk/tools`) as eve tools, one per file. `create_spend_request` asks the person in eve before Link asks them again; every result is stripped of card fields.
|
|
10
11
|
- **`linkCliAuth()`** — the owner's `link-cli` sign-in as the agent's wallet
|
|
11
12
|
credential. The owner runs `npx link-cli auth login --client-name <agent>` on
|
|
12
13
|
the agent's host once (a device-code flow approved in the Link app); the CLI
|
|
@@ -18,9 +19,9 @@ package adds the two pieces that make it usable by a Kybernesis agent:
|
|
|
18
19
|
own browser via `@kybernesis/computer`'s fill primitive. The model supplies CSS
|
|
19
20
|
selectors; the card goes Link → page and comes back only as brand + last four.
|
|
20
21
|
|
|
21
|
-
```ts title="agent/
|
|
22
|
-
import {
|
|
23
|
-
export default
|
|
22
|
+
```ts title="agent/tools/create_spend_request.ts"
|
|
23
|
+
import { linkTool } from "@kybernesis/payments";
|
|
24
|
+
export default linkTool("create_spend_request"); // one file per Link tool; see LINK_TOOL_NAMES
|
|
24
25
|
```
|
|
25
26
|
|
|
26
27
|
```ts title="agent/tools/pay_on_computer.ts"
|
|
@@ -42,9 +43,7 @@ approval link, wait, fill, screenshot, submit, report with `link__create_report`
|
|
|
42
43
|
|
|
43
44
|
## Gotchas
|
|
44
45
|
|
|
45
|
-
- `@stripe/link-integrations-eve
|
|
46
|
-
as a plain extension and have been exercised on eve 0.68, but a Link API change
|
|
47
|
-
shows up there first.
|
|
46
|
+
- Stripe's own eve extension (`@stripe/link-integrations-eve`, built with eve 0.66) does not mount on eve 0.68 — that is why the tools are wrapped here from the SDK instead.
|
|
48
47
|
- Link may require 3-D Secure or a verification step the agent cannot complete.
|
|
49
48
|
The instructions tell it to stop and describe the screen; the person can take
|
|
50
49
|
over the computer.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The approval moment of a purchase: the pure half (no Node imports), safe
|
|
3
|
+
* inside a workflow driver bundle. The Link calls live in ./approve-steps.js.
|
|
4
|
+
*
|
|
5
|
+
* Link approves a spend request in the person's Link app. Before this the
|
|
6
|
+
* agent pasted the approval link as text and asked them to say when they were
|
|
7
|
+
* done. Now the tool asks through eve's question channel with a marker a
|
|
8
|
+
* Kybernesis client renders as a card — merchant, total, an "Approve in Link"
|
|
9
|
+
* button — and parks until they tap "I approved" or "Cancel". Then it checks
|
|
10
|
+
* with Link and returns the real status. The model sees one result.
|
|
11
|
+
*/
|
|
12
|
+
export declare const SPEND_REQUEST_MARKER = "[kyb:spend-request]";
|
|
13
|
+
export interface SpendRequestAsk {
|
|
14
|
+
readonly id: string;
|
|
15
|
+
readonly amount: number;
|
|
16
|
+
readonly currency: string;
|
|
17
|
+
readonly merchant?: string;
|
|
18
|
+
readonly merchant_url?: string;
|
|
19
|
+
readonly approval_url?: string;
|
|
20
|
+
readonly status: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function spendRequestPrompt(ask: SpendRequestAsk): string;
|
|
23
|
+
export declare function parseSpendRequestPrompt(prompt: string): {
|
|
24
|
+
ask: SpendRequestAsk;
|
|
25
|
+
text: string;
|
|
26
|
+
} | null;
|
|
27
|
+
export interface SpendRequestSnapshot {
|
|
28
|
+
id: string;
|
|
29
|
+
status: string;
|
|
30
|
+
amount: number;
|
|
31
|
+
currency: string;
|
|
32
|
+
merchant?: string;
|
|
33
|
+
merchant_url?: string;
|
|
34
|
+
approval_url?: string;
|
|
35
|
+
}
|
|
36
|
+
/** What the agent's tool file passes to `ctx.ask` once it has a snapshot. The tool itself is authored in `agent/tools/` — eve compiles `"use workflow"` from source. */
|
|
37
|
+
export declare function spendRequestAsk(current: SpendRequestSnapshot): {
|
|
38
|
+
prompt: string;
|
|
39
|
+
display: "confirmation";
|
|
40
|
+
options: ({
|
|
41
|
+
id: string;
|
|
42
|
+
label: string;
|
|
43
|
+
style: "primary";
|
|
44
|
+
} | {
|
|
45
|
+
id: string;
|
|
46
|
+
label: string;
|
|
47
|
+
style: "danger";
|
|
48
|
+
})[];
|
|
49
|
+
};
|
|
50
|
+
export declare const APPROVE_SPEND_REQUEST_DESCRIPTION = "Put a spend request in front of the person for approval and wait for their answer. Call this right after create_spend_request. It shows them the merchant, the total and the Link approval button, pauses until they confirm or cancel, then returns the request's real status from Link. Only proceed to pay_on_computer when it returns approved.";
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The approval moment of a purchase: the pure half (no Node imports), safe
|
|
3
|
+
* inside a workflow driver bundle. The Link calls live in ./approve-steps.js.
|
|
4
|
+
*
|
|
5
|
+
* Link approves a spend request in the person's Link app. Before this the
|
|
6
|
+
* agent pasted the approval link as text and asked them to say when they were
|
|
7
|
+
* done. Now the tool asks through eve's question channel with a marker a
|
|
8
|
+
* Kybernesis client renders as a card — merchant, total, an "Approve in Link"
|
|
9
|
+
* button — and parks until they tap "I approved" or "Cancel". Then it checks
|
|
10
|
+
* with Link and returns the real status. The model sees one result.
|
|
11
|
+
*/
|
|
12
|
+
export const SPEND_REQUEST_MARKER = "[kyb:spend-request]";
|
|
13
|
+
export function spendRequestPrompt(ask) {
|
|
14
|
+
const total = `${(ask.amount / 100).toFixed(2)} ${ask.currency.toUpperCase()}`;
|
|
15
|
+
const at = ask.merchant ? ` at ${ask.merchant}` : "";
|
|
16
|
+
const link = ask.approval_url ? ` Approve it in Link: ${ask.approval_url}` : "";
|
|
17
|
+
return `${SPEND_REQUEST_MARKER} ${JSON.stringify(ask)}\nA purchase of ${total}${at} is waiting for your approval.${link}`;
|
|
18
|
+
}
|
|
19
|
+
export function parseSpendRequestPrompt(prompt) {
|
|
20
|
+
if (!prompt.startsWith(SPEND_REQUEST_MARKER))
|
|
21
|
+
return null;
|
|
22
|
+
const nl = prompt.indexOf("\n");
|
|
23
|
+
const head = nl === -1 ? prompt : prompt.slice(0, nl);
|
|
24
|
+
try {
|
|
25
|
+
return { ask: JSON.parse(head.slice(SPEND_REQUEST_MARKER.length).trim()), text: nl === -1 ? "" : prompt.slice(nl + 1) };
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** What the agent's tool file passes to `ctx.ask` once it has a snapshot. The tool itself is authored in `agent/tools/` — eve compiles `"use workflow"` from source. */
|
|
32
|
+
export function spendRequestAsk(current) {
|
|
33
|
+
return {
|
|
34
|
+
prompt: spendRequestPrompt(current),
|
|
35
|
+
display: "confirmation",
|
|
36
|
+
options: [
|
|
37
|
+
{ id: "approved", label: "I approved it in Link", style: "primary" },
|
|
38
|
+
{ id: "cancel", label: "Cancel this purchase", style: "danger" },
|
|
39
|
+
],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export const APPROVE_SPEND_REQUEST_DESCRIPTION = "Put a spend request in front of the person for approval and wait for their answer. Call this right after create_spend_request. It shows them the merchant, the total and the Link approval button, pauses until they confirm or cancel, then returns the request's real status from Link. Only proceed to pay_on_computer when it returns approved.";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { LinkCliAuthOptions } from "./link-cli-auth.js";
|
|
2
|
+
import type { SpendRequestSnapshot } from "./approve-ask.js";
|
|
3
|
+
/** The Link calls behind approve_spend_request. Import only from "use step" functions: this touches the filesystem and the network. */
|
|
4
|
+
export declare function spendRequestSnapshot(id: string, options?: LinkCliAuthOptions): Promise<SpendRequestSnapshot | null>;
|
|
5
|
+
export declare function spendRequestApprovalLink(id: string, options?: LinkCliAuthOptions): Promise<string | undefined>;
|
|
6
|
+
/** Link settles a moment after the tap; look a few times before giving up. */
|
|
7
|
+
export declare function spendRequestSettledStatus(id: string, options?: LinkCliAuthOptions): Promise<string>;
|
|
8
|
+
export declare function cancelSpendRequest(id: string, options?: LinkCliAuthOptions): Promise<void>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { linkClient } from "./link-tools.js";
|
|
2
|
+
/** The Link calls behind approve_spend_request. Import only from "use step" functions: this touches the filesystem and the network. */
|
|
3
|
+
export async function spendRequestSnapshot(id, options = {}) {
|
|
4
|
+
const link = linkClient(options);
|
|
5
|
+
const r = (await link.spendRequests.retrieve(id));
|
|
6
|
+
if (!r)
|
|
7
|
+
return null;
|
|
8
|
+
return { id: r.id, status: r.status, amount: r.amount, currency: r.currency, merchant: r.merchant_name ?? r.merchant, merchant_url: r.merchant_url, approval_url: r.approval_url };
|
|
9
|
+
}
|
|
10
|
+
export async function spendRequestApprovalLink(id, options = {}) {
|
|
11
|
+
const link = linkClient(options);
|
|
12
|
+
try {
|
|
13
|
+
const r = await link.spendRequests.requestApproval(id);
|
|
14
|
+
return r.approval_url;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** Link settles a moment after the tap; look a few times before giving up. */
|
|
21
|
+
export async function spendRequestSettledStatus(id, options = {}) {
|
|
22
|
+
const link = linkClient(options);
|
|
23
|
+
for (let i = 0; i < 6; i++) {
|
|
24
|
+
const r = await link.spendRequests.retrieve(id);
|
|
25
|
+
if (r && r.status !== "pending_approval" && r.status !== "created")
|
|
26
|
+
return r.status;
|
|
27
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
28
|
+
}
|
|
29
|
+
const r = await link.spendRequests.retrieve(id);
|
|
30
|
+
return r?.status ?? "unknown";
|
|
31
|
+
}
|
|
32
|
+
export async function cancelSpendRequest(id, options = {}) {
|
|
33
|
+
try {
|
|
34
|
+
await linkClient(options).spendRequests.cancel(id);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
/* already terminal */
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { LinkCliAuthOptions } from "./link-cli-auth.js";
|
|
2
|
+
/**
|
|
3
|
+
* The approval moment of a purchase, as one durable tool call.
|
|
4
|
+
*
|
|
5
|
+
* Link approves a spend request in the person's Link app. Before this the
|
|
6
|
+
* agent pasted the approval link as text and asked them to say when they were
|
|
7
|
+
* done. Now the tool asks through eve's question channel with a marker a
|
|
8
|
+
* Kybernesis client renders as a card — merchant, total, an "Approve in Link"
|
|
9
|
+
* button — and parks until they tap "I approved" or "Cancel". Then it checks
|
|
10
|
+
* with Link and returns the real status. The model sees one result.
|
|
11
|
+
*/
|
|
12
|
+
export declare const SPEND_REQUEST_MARKER = "[kyb:spend-request]";
|
|
13
|
+
export interface SpendRequestAsk {
|
|
14
|
+
readonly id: string;
|
|
15
|
+
readonly amount: number;
|
|
16
|
+
readonly currency: string;
|
|
17
|
+
readonly merchant?: string;
|
|
18
|
+
readonly merchant_url?: string;
|
|
19
|
+
readonly approval_url?: string;
|
|
20
|
+
readonly status: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function spendRequestPrompt(ask: SpendRequestAsk): string;
|
|
23
|
+
export declare function parseSpendRequestPrompt(prompt: string): {
|
|
24
|
+
ask: SpendRequestAsk;
|
|
25
|
+
text: string;
|
|
26
|
+
} | null;
|
|
27
|
+
export interface SpendRequestSnapshot {
|
|
28
|
+
id: string;
|
|
29
|
+
status: string;
|
|
30
|
+
amount: number;
|
|
31
|
+
currency: string;
|
|
32
|
+
merchant?: string;
|
|
33
|
+
merchant_url?: string;
|
|
34
|
+
approval_url?: string;
|
|
35
|
+
}
|
|
36
|
+
export declare function spendRequestSnapshot(id: string, options?: LinkCliAuthOptions): Promise<SpendRequestSnapshot | null>;
|
|
37
|
+
export declare function spendRequestApprovalLink(id: string, options?: LinkCliAuthOptions): Promise<string | undefined>;
|
|
38
|
+
/** Link settles a moment after the tap; look a few times before giving up. */
|
|
39
|
+
export declare function spendRequestSettledStatus(id: string, options?: LinkCliAuthOptions): Promise<string>;
|
|
40
|
+
export declare function cancelSpendRequest(id: string, options?: LinkCliAuthOptions): Promise<void>;
|
|
41
|
+
/** What the agent's tool file passes to `ctx.ask` once it has a snapshot. The tool itself is authored in `agent/tools/` — eve compiles `"use workflow"` from source. */
|
|
42
|
+
export declare function spendRequestAsk(current: SpendRequestSnapshot): {
|
|
43
|
+
prompt: string;
|
|
44
|
+
display: "confirmation";
|
|
45
|
+
options: ({
|
|
46
|
+
id: string;
|
|
47
|
+
label: string;
|
|
48
|
+
style: "primary";
|
|
49
|
+
} | {
|
|
50
|
+
id: string;
|
|
51
|
+
label: string;
|
|
52
|
+
style: "danger";
|
|
53
|
+
})[];
|
|
54
|
+
};
|
|
55
|
+
export declare const APPROVE_SPEND_REQUEST_DESCRIPTION = "Put a spend request in front of the person for approval and wait for their answer. Call this right after create_spend_request. It shows them the merchant, the total and the Link approval button, pauses until they confirm or cancel, then returns the request's real status from Link. Only proceed to pay_on_computer when it returns approved.";
|
package/dist/approve.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { linkClient } from "./link-tools.js";
|
|
2
|
+
/**
|
|
3
|
+
* The approval moment of a purchase, as one durable tool call.
|
|
4
|
+
*
|
|
5
|
+
* Link approves a spend request in the person's Link app. Before this the
|
|
6
|
+
* agent pasted the approval link as text and asked them to say when they were
|
|
7
|
+
* done. Now the tool asks through eve's question channel with a marker a
|
|
8
|
+
* Kybernesis client renders as a card — merchant, total, an "Approve in Link"
|
|
9
|
+
* button — and parks until they tap "I approved" or "Cancel". Then it checks
|
|
10
|
+
* with Link and returns the real status. The model sees one result.
|
|
11
|
+
*/
|
|
12
|
+
export const SPEND_REQUEST_MARKER = "[kyb:spend-request]";
|
|
13
|
+
export function spendRequestPrompt(ask) {
|
|
14
|
+
const total = `${(ask.amount / 100).toFixed(2)} ${ask.currency.toUpperCase()}`;
|
|
15
|
+
const at = ask.merchant ? ` at ${ask.merchant}` : "";
|
|
16
|
+
const link = ask.approval_url ? ` Approve it in Link: ${ask.approval_url}` : "";
|
|
17
|
+
return `${SPEND_REQUEST_MARKER} ${JSON.stringify(ask)}\nA purchase of ${total}${at} is waiting for your approval.${link}`;
|
|
18
|
+
}
|
|
19
|
+
export function parseSpendRequestPrompt(prompt) {
|
|
20
|
+
if (!prompt.startsWith(SPEND_REQUEST_MARKER))
|
|
21
|
+
return null;
|
|
22
|
+
const nl = prompt.indexOf("\n");
|
|
23
|
+
const head = nl === -1 ? prompt : prompt.slice(0, nl);
|
|
24
|
+
try {
|
|
25
|
+
return { ask: JSON.parse(head.slice(SPEND_REQUEST_MARKER.length).trim()), text: nl === -1 ? "" : prompt.slice(nl + 1) };
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export async function spendRequestSnapshot(id, options = {}) {
|
|
32
|
+
const link = linkClient(options);
|
|
33
|
+
const r = (await link.spendRequests.retrieve(id));
|
|
34
|
+
if (!r)
|
|
35
|
+
return null;
|
|
36
|
+
return { id: r.id, status: r.status, amount: r.amount, currency: r.currency, merchant: r.merchant_name ?? r.merchant, merchant_url: r.merchant_url, approval_url: r.approval_url };
|
|
37
|
+
}
|
|
38
|
+
export async function spendRequestApprovalLink(id, options = {}) {
|
|
39
|
+
const link = linkClient(options);
|
|
40
|
+
try {
|
|
41
|
+
const r = await link.spendRequests.requestApproval(id);
|
|
42
|
+
return r.approval_url;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** Link settles a moment after the tap; look a few times before giving up. */
|
|
49
|
+
export async function spendRequestSettledStatus(id, options = {}) {
|
|
50
|
+
const link = linkClient(options);
|
|
51
|
+
for (let i = 0; i < 6; i++) {
|
|
52
|
+
const r = await link.spendRequests.retrieve(id);
|
|
53
|
+
if (r && r.status !== "pending_approval" && r.status !== "created")
|
|
54
|
+
return r.status;
|
|
55
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
56
|
+
}
|
|
57
|
+
const r = await link.spendRequests.retrieve(id);
|
|
58
|
+
return r?.status ?? "unknown";
|
|
59
|
+
}
|
|
60
|
+
export async function cancelSpendRequest(id, options = {}) {
|
|
61
|
+
try {
|
|
62
|
+
await linkClient(options).spendRequests.cancel(id);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
/* already terminal */
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** What the agent's tool file passes to `ctx.ask` once it has a snapshot. The tool itself is authored in `agent/tools/` — eve compiles `"use workflow"` from source. */
|
|
69
|
+
export function spendRequestAsk(current) {
|
|
70
|
+
return {
|
|
71
|
+
prompt: spendRequestPrompt(current),
|
|
72
|
+
display: "confirmation",
|
|
73
|
+
options: [
|
|
74
|
+
{ id: "approved", label: "I approved it in Link", style: "primary" },
|
|
75
|
+
{ id: "cancel", label: "Cancel this purchase", style: "danger" },
|
|
76
|
+
],
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
export const APPROVE_SPEND_REQUEST_DESCRIPTION = "Put a spend request in front of the person for approval and wait for their answer. Call this right after create_spend_request. It shows them the merchant, the total and the Link approval button, pauses until they confirm or cancel, then returns the request's real status from Link. Only proceed to pay_on_computer when it returns approved.";
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { linkCliAuth, linkCliAuthFile, readStoredAuth, NOT_SIGNED_IN, type LinkCliAuthOptions } from "./link-cli-auth.js";
|
|
2
2
|
export { payOnComputerTool } from "./pay-on-computer.js";
|
|
3
3
|
export { PAYMENTS_INSTRUCTIONS } from "./instructions.js";
|
|
4
|
-
export {
|
|
4
|
+
export { linkTool, linkClient, sanitizeLinkOutput, LINK_TOOL_NAMES, type LinkToolName } from "./link-tools.js";
|
|
5
|
+
export { spendRequestAsk, spendRequestPrompt, parseSpendRequestPrompt, APPROVE_SPEND_REQUEST_DESCRIPTION, SPEND_REQUEST_MARKER, type SpendRequestAsk, type SpendRequestSnapshot } from "./approve-ask.js";
|
|
6
|
+
export { spendRequestSnapshot, spendRequestApprovalLink, spendRequestSettledStatus, cancelSpendRequest } from "./approve-steps.js";
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { linkCliAuth, linkCliAuthFile, readStoredAuth, NOT_SIGNED_IN } from "./link-cli-auth.js";
|
|
2
2
|
export { payOnComputerTool } from "./pay-on-computer.js";
|
|
3
3
|
export { PAYMENTS_INSTRUCTIONS } from "./instructions.js";
|
|
4
|
-
export {
|
|
4
|
+
export { linkTool, linkClient, sanitizeLinkOutput, LINK_TOOL_NAMES } from "./link-tools.js";
|
|
5
|
+
export { spendRequestAsk, spendRequestPrompt, parseSpendRequestPrompt, APPROVE_SPEND_REQUEST_DESCRIPTION, SPEND_REQUEST_MARKER } from "./approve-ask.js";
|
|
6
|
+
export { spendRequestSnapshot, spendRequestApprovalLink, spendRequestSettledStatus, cancelSpendRequest } from "./approve-steps.js";
|
package/dist/instructions.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const PAYMENTS_INSTRUCTIONS = "## Paying for things\n\nYou can buy things with the person's Link wallet. Every purchase is their decision twice over: creating a spend request asks them here, and Link asks them again in their Link app before any card exists. Never work around either step.\n\nThe order, every time:\n1. Agree the exact purchase first: merchant, items, final total including tax and shipping, in the right currency. Get these from the actual checkout page in your own browser (open_browser, then look), not from memory.\n2. `
|
|
1
|
+
export declare const PAYMENTS_INSTRUCTIONS = "## Paying for things\n\nYou can buy things with the person's Link wallet. Every purchase is their decision twice over: creating a spend request asks them here, and Link asks them again in their Link app before any card exists. Never work around either step.\n\nThe order, every time:\n1. Agree the exact purchase first: merchant, items, final total including tax and shipping, in the right currency. Get these from the actual checkout page in your own browser (open_browser, then look), not from memory.\n2. `create_spend_request` with that merchant, those line items and that total, then immediately `approve_spend_request` with its id. That shows the person the purchase with an approval button and waits for them; do not paste the approval link yourself and do not create a second request for the same purchase.\n3. When `approve_spend_request` returns approved, use `pay_on_computer` to type the one-time card into the checkout. You give it the selectors of the card fields you can see; the card itself never comes to you. Check the filled form in a screenshot, confirm the total still matches, then submit.\n4. Report the outcome with `create_report`: succeeded, failed, or what the merchant said. If the merchant needs more (3-D Secure, a code), stop and tell the person exactly what is on the screen; they can take over your screen.\n\nNever read card numbers or CVCs aloud, never paste them into chat or a file, never guess a total. If Link asks for verification you cannot complete, say so.\n";
|
package/dist/instructions.js
CHANGED
|
@@ -4,9 +4,9 @@ You can buy things with the person's Link wallet. Every purchase is their decisi
|
|
|
4
4
|
|
|
5
5
|
The order, every time:
|
|
6
6
|
1. Agree the exact purchase first: merchant, items, final total including tax and shipping, in the right currency. Get these from the actual checkout page in your own browser (open_browser, then look), not from memory.
|
|
7
|
-
2. \`
|
|
8
|
-
3. When \`
|
|
9
|
-
4. Report the outcome with \`
|
|
7
|
+
2. \`create_spend_request\` with that merchant, those line items and that total, then immediately \`approve_spend_request\` with its id. That shows the person the purchase with an approval button and waits for them; do not paste the approval link yourself and do not create a second request for the same purchase.
|
|
8
|
+
3. When \`approve_spend_request\` returns approved, use \`pay_on_computer\` to type the one-time card into the checkout. You give it the selectors of the card fields you can see; the card itself never comes to you. Check the filled form in a screenshot, confirm the total still matches, then submit.
|
|
9
|
+
4. Report the outcome with \`create_report\`: succeeded, failed, or what the merchant said. If the merchant needs more (3-D Secure, a code), stop and tell the person exactly what is on the screen; they can take over your screen.
|
|
10
10
|
|
|
11
11
|
Never read card numbers or CVCs aloud, never paste them into chat or a file, never guess a total. If Link asks for verification you cannot complete, say so.
|
|
12
12
|
`;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Link } from "@stripe/link-sdk";
|
|
2
|
+
import { type ToolDefinition } from "eve/tools";
|
|
3
|
+
import { type LinkCliAuthOptions } from "./link-cli-auth.js";
|
|
4
|
+
/**
|
|
5
|
+
* Stripe's Link tools, mounted the Kybernesis way.
|
|
6
|
+
*
|
|
7
|
+
* Stripe ships an eve extension, but it is built against an older eve and
|
|
8
|
+
* eve 0.68 refuses to mount it. The same tool catalog is available
|
|
9
|
+
* framework-free from `@stripe/link-sdk/tools`, so each one is wrapped here as
|
|
10
|
+
* an eve tool: the owner's link-cli sign-in supplies the token on every call,
|
|
11
|
+
* creating a spend request asks the person in eve before it asks them in Link,
|
|
12
|
+
* and anything that could carry a card number is stripped from what the model
|
|
13
|
+
* sees — the card has exactly one path, `pay_on_computer`.
|
|
14
|
+
*/
|
|
15
|
+
export declare const LINK_TOOL_NAMES: readonly ["retrieve_user_info", "list_payment_methods", "list_shipping_addresses", "list_spend_requests", "create_spend_request", "update_spend_request", "cancel_spend_request", "request_spend_approval", "retrieve_spend_request", "create_report"];
|
|
16
|
+
export type LinkToolName = (typeof LINK_TOOL_NAMES)[number];
|
|
17
|
+
/** A Link client whose token is the owner's current link-cli sign-in, read at call time. */
|
|
18
|
+
export declare function linkClient(options?: LinkCliAuthOptions): Link;
|
|
19
|
+
/** Remove anything that is or contains a card credential before it reaches the model. */
|
|
20
|
+
export declare function sanitizeLinkOutput<T>(value: T): T;
|
|
21
|
+
/**
|
|
22
|
+
* One Link tool as an eve tool. Mount each in `agent/tools/<name>.ts`:
|
|
23
|
+
*
|
|
24
|
+
* ```ts
|
|
25
|
+
* import { linkTool } from "@kybernesis/payments";
|
|
26
|
+
* export default linkTool("create_spend_request");
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare function linkTool<N extends LinkToolName>(name: N, options?: LinkCliAuthOptions): ToolDefinition<Record<string, unknown>, unknown>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Link } from "@stripe/link-sdk";
|
|
2
|
+
import { createLinkTools } from "@stripe/link-sdk/tools";
|
|
3
|
+
import { defineTool } from "eve/tools";
|
|
4
|
+
import { always, never } from "eve/tools/approval";
|
|
5
|
+
import { linkCliAuth } from "./link-cli-auth.js";
|
|
6
|
+
/**
|
|
7
|
+
* Stripe's Link tools, mounted the Kybernesis way.
|
|
8
|
+
*
|
|
9
|
+
* Stripe ships an eve extension, but it is built against an older eve and
|
|
10
|
+
* eve 0.68 refuses to mount it. The same tool catalog is available
|
|
11
|
+
* framework-free from `@stripe/link-sdk/tools`, so each one is wrapped here as
|
|
12
|
+
* an eve tool: the owner's link-cli sign-in supplies the token on every call,
|
|
13
|
+
* creating a spend request asks the person in eve before it asks them in Link,
|
|
14
|
+
* and anything that could carry a card number is stripped from what the model
|
|
15
|
+
* sees — the card has exactly one path, `pay_on_computer`.
|
|
16
|
+
*/
|
|
17
|
+
export const LINK_TOOL_NAMES = [
|
|
18
|
+
"retrieve_user_info",
|
|
19
|
+
"list_payment_methods",
|
|
20
|
+
"list_shipping_addresses",
|
|
21
|
+
"list_spend_requests",
|
|
22
|
+
"create_spend_request",
|
|
23
|
+
"update_spend_request",
|
|
24
|
+
"cancel_spend_request",
|
|
25
|
+
"request_spend_approval",
|
|
26
|
+
"retrieve_spend_request",
|
|
27
|
+
"create_report",
|
|
28
|
+
];
|
|
29
|
+
/** A Link client whose token is the owner's current link-cli sign-in, read at call time. */
|
|
30
|
+
export function linkClient(options = {}) {
|
|
31
|
+
const auth = linkCliAuth(options);
|
|
32
|
+
return new Link({ getAccessToken: async () => (await auth.getToken({})).token });
|
|
33
|
+
}
|
|
34
|
+
/** Remove anything that is or contains a card credential before it reaches the model. */
|
|
35
|
+
export function sanitizeLinkOutput(value) {
|
|
36
|
+
if (Array.isArray(value))
|
|
37
|
+
return value.map((v) => sanitizeLinkOutput(v));
|
|
38
|
+
if (value && typeof value === "object") {
|
|
39
|
+
const out = {};
|
|
40
|
+
for (const [k, v] of Object.entries(value)) {
|
|
41
|
+
if (k === "card" || k === "credential" || k === "number" || k === "cvc" || k === "payment_token" || k === "shared_payment_token")
|
|
42
|
+
continue;
|
|
43
|
+
out[k] = sanitizeLinkOutput(v);
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* One Link tool as an eve tool. Mount each in `agent/tools/<name>.ts`:
|
|
51
|
+
*
|
|
52
|
+
* ```ts
|
|
53
|
+
* import { linkTool } from "@kybernesis/payments";
|
|
54
|
+
* export default linkTool("create_spend_request");
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export function linkTool(name, options = {}) {
|
|
58
|
+
const catalog = createLinkTools(linkClient(options));
|
|
59
|
+
const tool = catalog[name];
|
|
60
|
+
return defineTool({
|
|
61
|
+
description: tool.description,
|
|
62
|
+
inputSchema: tool.inputSchema,
|
|
63
|
+
// Asking Link for money starts with asking the person here. Reading and
|
|
64
|
+
// reporting do not; Link's own approval step still gates every card.
|
|
65
|
+
approval: name === "create_spend_request" ? always() : never(),
|
|
66
|
+
async execute(input, ctx) {
|
|
67
|
+
const result = await tool.execute(input, ctx);
|
|
68
|
+
return sanitizeLinkOutput(result);
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { LinkCliAuthOptions } from "./link-cli-auth.js";
|
|
2
2
|
/**
|
|
3
3
|
* Type the one-time card of an APPROVED Link spend request into a checkout
|
|
4
4
|
* form on the agent's own computer. The card number and CVC go from Link to
|
|
@@ -14,7 +14,7 @@ export declare function payOnComputerTool(options?: LinkCliAuthOptions): import(
|
|
|
14
14
|
spend_request_id: string;
|
|
15
15
|
page_url: string;
|
|
16
16
|
fields: {
|
|
17
|
-
field: "number" | "
|
|
17
|
+
field: "number" | "cvc" | "name" | "exp_month" | "exp_year" | "expiration" | "postal_code";
|
|
18
18
|
selector: string;
|
|
19
19
|
frameUrl?: string | undefined;
|
|
20
20
|
format?: "MM/YY" | "MM/YYYY" | undefined;
|
|
@@ -33,7 +33,7 @@ export declare function payOnComputerTool(options?: LinkCliAuthOptions): import(
|
|
|
33
33
|
spend_request_id: string;
|
|
34
34
|
page_url: string;
|
|
35
35
|
fields: {
|
|
36
|
-
field: "number" | "
|
|
36
|
+
field: "number" | "cvc" | "name" | "exp_month" | "exp_year" | "expiration" | "postal_code";
|
|
37
37
|
selector: string;
|
|
38
38
|
frameUrl?: string | undefined;
|
|
39
39
|
format?: "MM/YY" | "MM/YYYY" | undefined;
|
package/dist/pay-on-computer.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { Link } from "@stripe/link-sdk";
|
|
2
1
|
import { fillOnComputer } from "@kybernesis/computer";
|
|
3
2
|
import { defineTool } from "eve/tools";
|
|
4
3
|
import { never } from "eve/tools/approval";
|
|
5
4
|
import { z } from "zod";
|
|
6
|
-
import {
|
|
5
|
+
import { linkClient } from "./link-tools.js";
|
|
7
6
|
const fieldSchema = z.object({
|
|
8
7
|
field: z.enum(["name", "number", "exp_month", "exp_year", "expiration", "cvc", "postal_code"]),
|
|
9
8
|
selector: z.string().min(1).max(500).describe("CSS selector of that input on the checkout page."),
|
|
@@ -22,7 +21,7 @@ const fieldSchema = z.object({
|
|
|
22
21
|
* a request that is not approved is refused.
|
|
23
22
|
*/
|
|
24
23
|
export function payOnComputerTool(options = {}) {
|
|
25
|
-
const
|
|
24
|
+
const link = linkClient(options);
|
|
26
25
|
return defineTool({
|
|
27
26
|
description: "Type the one-time card from an APPROVED Link spend request into the checkout form open in your own browser. Give the CSS selector of each card field you can see (name, number, expiration or exp_month/exp_year, cvc, postal code). The card details never come back to you; the result says which fields were filled. Open the checkout with open_browser first and look at it.",
|
|
28
27
|
inputSchema: z.object({
|
|
@@ -32,8 +31,6 @@ export function payOnComputerTool(options = {}) {
|
|
|
32
31
|
}),
|
|
33
32
|
approval: never(),
|
|
34
33
|
async execute(input, ctx) {
|
|
35
|
-
const { token } = await auth.getToken({});
|
|
36
|
-
const link = new Link({ accessToken: token });
|
|
37
34
|
const request = await link.spendRequests.retrieve(input.spend_request_id, { include: ["card"] });
|
|
38
35
|
if (!request)
|
|
39
36
|
throw new Error("That spend request does not exist.");
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kybernesis/payments",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Stripe Link as an eve agent's wallet, on its owner's behalf: Link's tool catalog mounted as eve tools behind the owner's link-cli sign-in, the person's approval on every purchase, and a one-time card typed into a checkout on the agent's own computer without the model ever seeing it.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|
|
@@ -15,7 +15,17 @@
|
|
|
15
15
|
"import": "./dist/index.js",
|
|
16
16
|
"default": "./dist/index.js"
|
|
17
17
|
},
|
|
18
|
-
"./package.json": "./package.json"
|
|
18
|
+
"./package.json": "./package.json",
|
|
19
|
+
"./ask": {
|
|
20
|
+
"types": "./dist/approve-ask.d.ts",
|
|
21
|
+
"import": "./dist/approve-ask.js",
|
|
22
|
+
"default": "./dist/approve-ask.js"
|
|
23
|
+
},
|
|
24
|
+
"./steps": {
|
|
25
|
+
"types": "./dist/approve-steps.d.ts",
|
|
26
|
+
"import": "./dist/approve-steps.js",
|
|
27
|
+
"default": "./dist/approve-steps.js"
|
|
28
|
+
}
|
|
19
29
|
},
|
|
20
30
|
"files": [
|
|
21
31
|
"dist",
|
|
@@ -30,7 +40,6 @@
|
|
|
30
40
|
},
|
|
31
41
|
"dependencies": {
|
|
32
42
|
"@stripe/link-cli": "^0.26.0",
|
|
33
|
-
"@stripe/link-integrations-eve": "^0.2.4",
|
|
34
43
|
"@stripe/link-sdk": "^0.11.0"
|
|
35
44
|
},
|
|
36
45
|
"peerDependencies": {
|