path-terminal-init 0.2.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 +67 -0
- package/dist/cli.js +725 -0
- package/dist/init.js +6805 -0
- package/package.json +50 -0
- package/rules/android/path-integration-android.mdc +325 -0
- package/rules/ios/path-integration.mdc +393 -0
- package/rules/windows-10/path-integration-windows10.mdc +238 -0
- package/rules/windows-11/path-integration-windows11.mdc +230 -0
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "path-terminal-init",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "One-line setup for the Path Terminal SDK integration assistant — connects the user's AI agent to the Path MCP server and installs the per-OS integration recipe (iOS, Android, Windows). The single source of truth for the bootstrap CLI and the recipes.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"path-terminal-init": "dist/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"main": "dist/cli.js",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "node build.mjs",
|
|
11
|
+
"typecheck": "tsc --noEmit",
|
|
12
|
+
"dev": "ts-node src/cli.ts",
|
|
13
|
+
"prepublishOnly": "npm run build"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"rules"
|
|
18
|
+
],
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/keyman12/Path-terminal-init.git"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"chalk": "^5.3.0",
|
|
25
|
+
"commander": "^12.0.0",
|
|
26
|
+
"ora": "^8.0.1"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^20.11.5",
|
|
30
|
+
"esbuild": "^0.28.0",
|
|
31
|
+
"ts-node": "^10.9.2",
|
|
32
|
+
"typescript": "^5.3.3"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=18.0.0"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"path",
|
|
39
|
+
"terminal",
|
|
40
|
+
"sdk",
|
|
41
|
+
"payment",
|
|
42
|
+
"mcp",
|
|
43
|
+
"cursor",
|
|
44
|
+
"claude",
|
|
45
|
+
"ios",
|
|
46
|
+
"android",
|
|
47
|
+
"windows",
|
|
48
|
+
"epos"
|
|
49
|
+
]
|
|
50
|
+
}
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: >
|
|
3
|
+
Rules for AI agents integrating the Path Terminal SDK into an Android EPOS
|
|
4
|
+
application. Apply when the user asks to integrate payments, connect to
|
|
5
|
+
a payment terminal, wire a sale or refund, or set up the Path emulator.
|
|
6
|
+
globs:
|
|
7
|
+
- "**/*.kt"
|
|
8
|
+
- "**/AndroidManifest.xml"
|
|
9
|
+
- "**/settings.gradle.kts"
|
|
10
|
+
- "**/build.gradle.kts"
|
|
11
|
+
alwaysApply: false
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Path Terminal SDK — Android Integration Rules
|
|
15
|
+
|
|
16
|
+
You are integrating the Path Terminal SDK into an existing Android EPOS application.
|
|
17
|
+
This app is a **developer EPOS system** that replicates a production environment.
|
|
18
|
+
|
|
19
|
+
You have access to the Path MCP server at mcp.path2ai.tech:
|
|
20
|
+
- Tools: `get_code_example` (use platform='android'), `validate_integration` (platform='android'),
|
|
21
|
+
`get_integration_checklist` (platform='android'), `explain_error`, `get_manifest_requirements`
|
|
22
|
+
- Always call MCP tools before writing code. Never guess at SDK patterns.
|
|
23
|
+
|
|
24
|
+
> **Check whether the SDK is already wired before doing anything else.**
|
|
25
|
+
> Run:
|
|
26
|
+
> ```
|
|
27
|
+
> grep -r "path-terminal-sdk\|PathTerminal\|BLEPathTerminalAdapter" settings.gradle.kts app/build.gradle.kts 2>/dev/null | wc -l
|
|
28
|
+
> ```
|
|
29
|
+
> - **Count > 0** — SDK already present. Proceed directly to Step 1.
|
|
30
|
+
> - **Count = 0** — SDK not yet added. Add the Gradle composite build first (Step 0).
|
|
31
|
+
|
|
32
|
+
> **Language override.** Never write "Pico W" in any output or code comment.
|
|
33
|
+
> Always use "Path POS Emulator" when referring to the physical device.
|
|
34
|
+
|
|
35
|
+
> **Confirm the correct project directory first.** Run `pwd` before doing
|
|
36
|
+
> anything. If the path contains `.Trash`, `tmp`, or `worktree`, stop immediately.
|
|
37
|
+
> Ask the developer to relaunch Claude Code from the correct project directory.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Step 0 — Gradle setup (if SDK not yet installed)
|
|
42
|
+
|
|
43
|
+
Call `get_code_example` with operation='install' and platform='android'.
|
|
44
|
+
|
|
45
|
+
1. **settings.gradle.kts** — add below `include(":app")`:
|
|
46
|
+
```kotlin
|
|
47
|
+
includeBuild("../path-terminal-sdk-android") {
|
|
48
|
+
dependencySubstitution {
|
|
49
|
+
substitute(module("tech.path2ai.sdk:path-core-models")).using(project(":path-core-models"))
|
|
50
|
+
substitute(module("tech.path2ai.sdk:path-terminal-sdk")).using(project(":path-terminal-sdk"))
|
|
51
|
+
substitute(module("tech.path2ai.sdk:path-emulator-adapter")).using(project(":path-emulator-adapter"))
|
|
52
|
+
substitute(module("tech.path2ai.sdk:path-psdk-adapter")).using(project(":path-psdk-adapter"))
|
|
53
|
+
substitute(module("tech.path2ai.sdk:path-mock-adapter")).using(project(":path-mock-adapter"))
|
|
54
|
+
substitute(module("tech.path2ai.sdk:path-diagnostics")).using(project(":path-diagnostics"))
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
2. **app/build.gradle.kts** — add to `dependencies {}`:
|
|
60
|
+
```kotlin
|
|
61
|
+
implementation("tech.path2ai.sdk:path-terminal-sdk:0.1.0")
|
|
62
|
+
implementation("tech.path2ai.sdk:path-emulator-adapter:0.1.0")
|
|
63
|
+
implementation("tech.path2ai.sdk:path-psdk-adapter:0.1.0") // Verifone backend (Step 2c)
|
|
64
|
+
```
|
|
65
|
+
The Verifone (`path-psdk-adapter`) backend bundles the Verifone aar, which requires
|
|
66
|
+
two extra build settings in the consuming app — call `get_code_example` with
|
|
67
|
+
operation='verifone-init' and platform='android' for the exact lines:
|
|
68
|
+
- register the SDK repo's `third-party/verifone/m2` maven repo **and** `https://jitpack.io`
|
|
69
|
+
- enable `buildFeatures { dataBinding = true }`
|
|
70
|
+
|
|
71
|
+
3. **AndroidManifest.xml** — call `get_manifest_requirements` for exact XML.
|
|
72
|
+
|
|
73
|
+
The path-terminal-sdk-android repo must exist as a sibling directory:
|
|
74
|
+
```
|
|
75
|
+
android/
|
|
76
|
+
├── path-terminal-sdk-android/ ← SDK (sibling)
|
|
77
|
+
└── YourApp/ ← this project
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Your job — four steps, in this order
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
### Step 1 — Disconnect the existing terminal wiring
|
|
87
|
+
|
|
88
|
+
Find the app entry point (Application class, MainActivity, or DI root) and **read the actual code**.
|
|
89
|
+
|
|
90
|
+
Check what adapter is currently injected into the terminal manager. A comment saying
|
|
91
|
+
*"replace with Path adapter"* does not mean it has been done — read the code.
|
|
92
|
+
|
|
93
|
+
**You are looking for this pattern:**
|
|
94
|
+
```kotlin
|
|
95
|
+
// THIS is wired to Path — correct:
|
|
96
|
+
terminalManager = TerminalManager(PathTerminalAdapter(this))
|
|
97
|
+
|
|
98
|
+
// THIS is NOT wired to Path — fix it:
|
|
99
|
+
terminalManager = TerminalManager(OCPayTerminalAdapter())
|
|
100
|
+
terminalManager = TerminalManager() // default adapter
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
If the call does not use `PathTerminalAdapter` (or equivalent Path SDK adapter), change it.
|
|
104
|
+
|
|
105
|
+
> **The adapter class must implement the existing `PaymentTerminalAdapter` interface.**
|
|
106
|
+
> Do not rename or remove the interface — it is the integration contract.
|
|
107
|
+
|
|
108
|
+
**Also disable any auto-connect on launch** — find calls to `.connect()` in
|
|
109
|
+
`onCreate`, `init`, or `onResume` that fire automatically. Remove them.
|
|
110
|
+
Path connects manually via Settings, not on launch.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
### Step 2 — Add a backend switcher to Settings
|
|
115
|
+
|
|
116
|
+
The purpose of this integration is: **prove the payment functions against the Path
|
|
117
|
+
POS Emulator, then let the user switch — themselves, in the app — to a real Verifone
|
|
118
|
+
terminal when ready.** Settings must let the user pick *which* terminal to connect to.
|
|
119
|
+
Do NOT hardcode one transport.
|
|
120
|
+
|
|
121
|
+
> **Connection details are entered by the user in the app, not by you.** You wire the
|
|
122
|
+
> switcher, the IP field, and the credential storage, ship sensible test defaults, and
|
|
123
|
+
> describe how to use it in your end-of-run summary (Step 4). You do NOT ask the user
|
|
124
|
+
> for an IP or password during this run.
|
|
125
|
+
|
|
126
|
+
Find the Settings screen (a Composable or Activity named Settings, Preferences, or
|
|
127
|
+
similar). Add a **Path POS Adapter** section with a backend picker offering **three**:
|
|
128
|
+
|
|
129
|
+
| Backend | Transport | Adapter | Needs |
|
|
130
|
+
|---|---|---|---|
|
|
131
|
+
| **Emulator (Wi-Fi)** — *default selection* | TCP/IP | `TcpPathTerminalAdapter(host = …)` | emulator IP |
|
|
132
|
+
| **Emulator (Bluetooth)** | BLE | `BLEPathTerminalAdapter(context = …)` | scan + pick device |
|
|
133
|
+
| **Verifone** | TCP/IP (PSDK) | `VerifonePSDKAdapter(context, config)` | terminal IP + stored login |
|
|
134
|
+
|
|
135
|
+
Mirror the demo app's proven pattern — **call `get_code_example` with operation
|
|
136
|
+
='backend-switch' and platform='android' first** and reproduce its `buildAdapter(backend)`
|
|
137
|
+
shape. All sale / refund / void / receipt code is backend-agnostic; only adapter
|
|
138
|
+
construction differs.
|
|
139
|
+
|
|
140
|
+
**Persist the user's selection and connection details** in `SharedPreferences` behind a
|
|
141
|
+
small settings type, with test defaults so a fresh clone connects without typing anything:
|
|
142
|
+
```kotlin
|
|
143
|
+
enum class TerminalBackend { EMULATOR_WIFI, EMULATOR_BLE, VERIFONE } // default EMULATOR_WIFI
|
|
144
|
+
// Stored keys: backend, emulator_host, verifone_host,
|
|
145
|
+
// login_username, login_password, login_shift, refund_password
|
|
146
|
+
// Test defaults: verifone_host "192.168.1.88", username "user",
|
|
147
|
+
// password "password123", shift "shift123"
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
#### Step 2a — Emulator over Wi-Fi (the default) and Verifone — IP entry
|
|
151
|
+
|
|
152
|
+
For the two IP-addressed backends, show a **host/IP text field** (label it "Emulator IP"
|
|
153
|
+
or "Verifone terminal IP" depending on the selection) plus an **Apply & Connect** button.
|
|
154
|
+
On connect, build the matching adapter from the stored host and rebuild `PathTerminal`
|
|
155
|
+
(disconnect the previous adapter first — real terminals and the emulator's Wi-Fi mode
|
|
156
|
+
allow only **one** client at a time). TCP and Verifone need no scan — `discoverDevices()`
|
|
157
|
+
returns one synthetic device for the configured host, so "Apply & Connect" connects directly.
|
|
158
|
+
|
|
159
|
+
#### Step 2b — Emulator over Bluetooth — scan and connect
|
|
160
|
+
|
|
161
|
+
For the BLE backend keep the scan/connect flow:
|
|
162
|
+
1. **Scan for Path Terminals** — triggers BLE discovery
|
|
163
|
+
2. Discovered devices list — each with a **Connect** button
|
|
164
|
+
3. Live connection state (Scanning / Connected / Disconnected)
|
|
165
|
+
4. **Disconnect** when connected
|
|
166
|
+
|
|
167
|
+
**Add to `PaymentTerminalAdapter` interface** (with no-op default implementations):
|
|
168
|
+
```kotlin
|
|
169
|
+
suspend fun scanForDevices(): List<TerminalDeviceInfo>
|
|
170
|
+
suspend fun connectToDevice(id: String)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Implement `PathTerminalAdapter`** — call `get_code_example` with operation='discover'
|
|
174
|
+
and platform='android' first. The SDK exposes `DiscoveredDevice` with `.id` and `.name`
|
|
175
|
+
properties — use them directly.
|
|
176
|
+
|
|
177
|
+
**Expose on the ViewModel:**
|
|
178
|
+
```kotlin
|
|
179
|
+
private val _discoveredDevices = MutableStateFlow<List<TerminalDeviceInfo>>(emptyList())
|
|
180
|
+
val discoveredDevices: StateFlow<List<TerminalDeviceInfo>> = _discoveredDevices.asStateFlow()
|
|
181
|
+
|
|
182
|
+
private val _isScanning = MutableStateFlow(false)
|
|
183
|
+
val isScanning: StateFlow<Boolean> = _isScanning.asStateFlow()
|
|
184
|
+
|
|
185
|
+
fun scanForDevices() {
|
|
186
|
+
viewModelScope.launch {
|
|
187
|
+
_isScanning.value = true
|
|
188
|
+
_discoveredDevices.value = adapter.scanForDevices()
|
|
189
|
+
_isScanning.value = false
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
#### Step 2c — Verifone backend and stored credentials
|
|
195
|
+
|
|
196
|
+
The Verifone backend connects to a real terminal over the PSDK. Call `get_code_example`
|
|
197
|
+
with operation='verifone-init' and platform='android' first. Construct it from a
|
|
198
|
+
`VerifoneTerminalConfig` built from the **stored** login fields — never hardcode
|
|
199
|
+
credentials inline:
|
|
200
|
+
```kotlin
|
|
201
|
+
import tech.path2ai.sdk.psdk.VerifonePSDKAdapter
|
|
202
|
+
import tech.path2ai.sdk.psdk.VerifoneTerminalConfig
|
|
203
|
+
|
|
204
|
+
val adapter = VerifonePSDKAdapter(
|
|
205
|
+
context = context,
|
|
206
|
+
config = VerifoneTerminalConfig(
|
|
207
|
+
host = settings.verifoneHost,
|
|
208
|
+
username = settings.loginUsername,
|
|
209
|
+
password = settings.loginPassword,
|
|
210
|
+
shift = settings.loginShift,
|
|
211
|
+
refundPassword = settings.refundPassword
|
|
212
|
+
)
|
|
213
|
+
)
|
|
214
|
+
```
|
|
215
|
+
Keep the Verifone login fields **separate from the emulator settings** (their own stored
|
|
216
|
+
keys, their own editable fields) — cleaner than reusing the emulator's. All terminals are
|
|
217
|
+
treated as **generic Verifone** (same AGPA app, same PSDK connect/login), so no
|
|
218
|
+
per-terminal configuration is needed beyond IP + login.
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
### Step 3 — Wire sale and refund through the Path SDK
|
|
223
|
+
|
|
224
|
+
Call `get_code_example` with operation='sale' and platform='android'.
|
|
225
|
+
Call `get_code_example` with operation='refund' and platform='android'.
|
|
226
|
+
|
|
227
|
+
**Implement `PathTerminalAdapter.submitSale()`:**
|
|
228
|
+
```kotlin
|
|
229
|
+
override suspend fun submitSale(request: TerminalSaleRequest): TerminalSaleResponse {
|
|
230
|
+
val envelope = RequestEnvelope.create(sdkVersion = "0.1.0", adapterVersion = "0.1.0")
|
|
231
|
+
val sdkRequest = TransactionRequest.sale(
|
|
232
|
+
amountMinor = request.amountPence,
|
|
233
|
+
currency = request.currencyCode,
|
|
234
|
+
envelope = envelope
|
|
235
|
+
)
|
|
236
|
+
val result = terminal.sale(sdkRequest)
|
|
237
|
+
return when (result.state) {
|
|
238
|
+
TransactionState.APPROVED -> TerminalSaleResponse(
|
|
239
|
+
authorised = true,
|
|
240
|
+
authorisationCode = result.cardLastFour, // map fields as needed
|
|
241
|
+
terminalReference = result.transactionId
|
|
242
|
+
)
|
|
243
|
+
else -> TerminalSaleResponse(
|
|
244
|
+
authorised = false,
|
|
245
|
+
failureReason = result.error?.message ?: result.state.name
|
|
246
|
+
)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**For refunds**, the success state is `TransactionState.REFUNDED` — **not** `APPROVED`.
|
|
252
|
+
Using `APPROVED` in a refund check will never match.
|
|
253
|
+
|
|
254
|
+
Call `validate_integration` with platform='android' on your code before finalising.
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
### Step 4 — Verify it works end to end
|
|
259
|
+
|
|
260
|
+
1. Confirm the Application/MainActivity injects `PathTerminalAdapter` (not the old adapter).
|
|
261
|
+
2. Confirm AndroidManifest.xml has BLUETOOTH_SCAN and BLUETOOTH_CONNECT permissions
|
|
262
|
+
(needed for the Bluetooth backend).
|
|
263
|
+
3. **Prove on the emulator first (the default path — Wi-Fi/IP):**
|
|
264
|
+
- Put the Path POS Emulator in Wi-Fi mode (Config → Connection on the device);
|
|
265
|
+
it shows its `IP:port` on the welcome screen.
|
|
266
|
+
- Run the app → Settings → Payment Terminal → backend **Emulator (Wi-Fi)** → type
|
|
267
|
+
that IP → **Apply & Connect**. connectionState should reach Connected.
|
|
268
|
+
- (Optionally also prove **Emulator (Bluetooth)**: select it, **Scan**, **Connect**.)
|
|
269
|
+
4. Add items to cart → Card payment → present a card / tap NFC tag on emulator when prompted.
|
|
270
|
+
5. Verify:
|
|
271
|
+
- result.state == TransactionState.APPROVED (or result.isApproved == true)
|
|
272
|
+
- result.transactionId is non-null
|
|
273
|
+
- Receipt data populated via terminal.getReceiptData(transactionId)
|
|
274
|
+
6. **Then prove the switch to Verifone:** select backend **Verifone**, enter the terminal's
|
|
275
|
+
IP and confirm the stored login, **Apply & Connect** — the SDK runs the PSDK init + login
|
|
276
|
+
and the same sale/refund/void flow runs against the real terminal, unchanged. (Only one
|
|
277
|
+
client may connect to a Verifone terminal at a time — disconnect any other POS first.)
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## SDK rules — always follow these
|
|
282
|
+
|
|
283
|
+
**ALWAYS:**
|
|
284
|
+
- Call `get_code_example` with platform='android' before writing any SDK code
|
|
285
|
+
- Use `TransactionRequest.sale(...)` and `TransactionRequest.refund(...)` factory methods
|
|
286
|
+
- Use `RequestEnvelope.create(sdkVersion, adapterVersion)` — never build manually
|
|
287
|
+
- Handle TransactionState: APPROVED, DECLINED, TIMED_OUT, FAILED, CANCELLED
|
|
288
|
+
- For **refunds**, success state is `REFUNDED` — not `APPROVED`
|
|
289
|
+
- Catch `PathError` and check `.recoverable`
|
|
290
|
+
- Call `validate_integration` with platform='android' before presenting code
|
|
291
|
+
- Call from coroutines — `terminal.sale()` and `terminal.refund()` are `suspend` functions
|
|
292
|
+
|
|
293
|
+
**NEVER:**
|
|
294
|
+
- Use the raw `TransactionRequest(...)` constructor — always use factory methods
|
|
295
|
+
- Store or log card data (cardLastFour is acceptable, nothing else)
|
|
296
|
+
- Retry a financial operation without a new `RequestEnvelope` (new idempotencyKey)
|
|
297
|
+
- Leave the existing default adapter in place — Path must be injected
|
|
298
|
+
- Leave auto-connect-on-launch running
|
|
299
|
+
- Skip `PathError` handling
|
|
300
|
+
- Call blocking I/O from the main thread — all SDK calls are suspend functions
|
|
301
|
+
- Wrap `terminal.sale()` in a timeout shorter than 30 seconds
|
|
302
|
+
|
|
303
|
+
**AMOUNTS:** Minor currency units (pence/cents) only as integers.
|
|
304
|
+
- `100` = £1.00 GBP · `1250` = £12.50 GBP · Never pass decimals.
|
|
305
|
+
|
|
306
|
+
**COROUTINES:** All SDK methods are `suspend`. Always call from `viewModelScope.launch { }` or equivalent.
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## After making changes
|
|
311
|
+
|
|
312
|
+
Summarise:
|
|
313
|
+
- Each file modified and the specific change made
|
|
314
|
+
- Confirm Step 1 (adapter injection) was completed
|
|
315
|
+
|
|
316
|
+
Then give **tailored connect instructions** — the user enters the connection details
|
|
317
|
+
themselves, so tell them exactly where you wired things in:
|
|
318
|
+
- The Settings screen / section name you added the backend switcher to
|
|
319
|
+
- How to connect to the emulator over **Wi-Fi/IP** (the default): which backend to pick,
|
|
320
|
+
where to type the emulator's IP, which button connects
|
|
321
|
+
- How to also connect over **Bluetooth** if they prefer (Scan → Connect)
|
|
322
|
+
- How to **switch to Verifone** when ready: pick Verifone, enter the terminal IP, confirm
|
|
323
|
+
the stored login, connect
|
|
324
|
+
|
|
325
|
+
Keep it brief and concrete — the user should be able to follow it without reading code.
|