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/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.