@reclaimprotocol/agent 0.1.12 → 0.1.14

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@reclaimprotocol/agent",
3
3
  "title": "Reclaim Agent",
4
- "version": "0.1.12",
4
+ "version": "0.1.14",
5
5
  "description": "Reclaim Protocol's tools for agents — Reclaim Protocol lets your app verify **data points** about its users — a follower count, an account balance, an order history. This package gives your AI coding agent the tools to **find, build, test and fix the _data verification providers_** (the recipes that verify a data point) by inspecting and replaying captured requests, checking sessions, evaluate proofs, publishing updates, and much more.",
6
6
  "type": "module",
7
7
  "main": "./lib/index.js",
@@ -41,6 +41,7 @@
41
41
  "src",
42
42
  "scripts",
43
43
  "skill",
44
+ "samples",
44
45
  "README.md"
45
46
  ],
46
47
  "engines": {
@@ -83,12 +84,12 @@
83
84
  "dependencies": {
84
85
  "@hapi/boom": "^9.1.4",
85
86
  "@modelcontextprotocol/sdk": "^1.29.0",
87
+ "@noble/hashes": "^2.2.0",
86
88
  "@reclaimprotocol/attestor-core": "^5.0.8",
87
- "@reclaimprotocol/client": "*",
89
+ "@reclaimprotocol/client": "0.1.14",
88
90
  "@reclaimprotocol/tls": "^0.1.3",
89
91
  "chrome-remote-interface": "^0.33.0",
90
92
  "domhandler": "^6.0.0",
91
- "ethers": "^6.17.0",
92
93
  "parse5": "^8.0.0",
93
94
  "parse5-htmlparser2-tree-adapter": "^8.0.0",
94
95
  "pino": "^9.14.0",
@@ -0,0 +1,12 @@
1
+ RECLAIM_APP_ID=
2
+ RECLAIM_APP_SECRET=
3
+ PORT=3000
4
+ # REQUIRED for the webhook callback to land — Reclaim's servers can't reach
5
+ # localhost. Pick ONE:
6
+ # - ngrok (or similar tunnel): run `ngrok http 3000` in a separate
7
+ # terminal and paste the https://<id>.ngrok-free.app URL it prints here.
8
+ # - A dev/staging server you can deploy this to quickly: point this at
9
+ # that server's public URL instead.
10
+ # Without either, only the frontend-side flow (opening requestUrl) works —
11
+ # the webhook never fires.
12
+ PUBLIC_URL=
@@ -0,0 +1,24 @@
1
+ # Reclaim demo server
2
+
3
+ 1. `npm install`
4
+ 2. Fill in `.env` (copy from `.env.example`) — get `RECLAIM_APP_ID`/
5
+ `RECLAIM_APP_SECRET` from the `issue_app_credentials` MCP tool, or your
6
+ existing app.
7
+ 3. Set `PUBLIC_URL` in `.env` to a URL Reclaim's servers can reach — pick
8
+ whichever fits:
9
+ - **ngrok**: in a separate terminal, run `ngrok http 3000` (install from
10
+ https://ngrok.com if needed) and copy the
11
+ `https://<id>.ngrok-free.app` URL it prints.
12
+ - **A dev/staging server you can deploy to quickly**: if you already have
13
+ one reachable from the internet, deploy this there and use its URL
14
+ instead — no local tunnel needed.
15
+ This step is REQUIRED — `localhost` is never reachable from Reclaim's
16
+ servers.
17
+ 4. `npm start`, then open http://localhost:3000/verify (or your deployed
18
+ URL's `/verify`) and click "Start verification".
19
+ 5. Watch the server logs for the `/webhooks/reclaim` payload once you
20
+ complete verification.
21
+
22
+ This demo defaults to providerId `"example"` — a built-in smoke-test
23
+ provider, so you can confirm the whole loop (request → open → callback)
24
+ works before wiring in a real provider.
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "reclaim-demo-server",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "start": "node server.js"
7
+ },
8
+ "dependencies": {
9
+ "@reclaimprotocol/js-sdk": "^5.8.1",
10
+ "dotenv": "^16.4.5",
11
+ "express": "^4.21.2"
12
+ }
13
+ }
@@ -0,0 +1,156 @@
1
+ import express from "express";
2
+ import "dotenv/config";
3
+ import { ReclaimProofRequest, verifyProof } from "@reclaimprotocol/js-sdk";
4
+ import { randomUUID } from "node:crypto";
5
+
6
+ const app = express();
7
+ const port = process.env.PORT || 3000;
8
+ const providerId = __PROVIDER_ID__;
9
+ // Left unpinned by default — set this if you need to pin a specific version.
10
+ const providerVersion = __PROVIDER_VERSION__;
11
+
12
+ app.set("trust proxy", true);
13
+ app.use(express.json({ limit: "2mb" }));
14
+
15
+ app.get("/health", (_req, res) => {
16
+ res.json({ ok: true });
17
+ });
18
+
19
+ app.get("/verify", (_req, res) => {
20
+ res.type("html").send(`<!doctype html>
21
+ <html lang="en">
22
+ <head>
23
+ <meta charset="utf-8" />
24
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
25
+ <title>Reclaim demo verification</title>
26
+ <style>
27
+ body {
28
+ font-family: system-ui, sans-serif; max-width: 680px;
29
+ margin: 64px auto; padding: 0 20px; line-height: 1.5;
30
+ }
31
+ code { background: #f3f4f6; padding: 2px 6px; border-radius: 4px; }
32
+ button {
33
+ background: #111827; border: 0; border-radius: 8px; color: white;
34
+ cursor: pointer; font: inherit; padding: 12px 18px;
35
+ }
36
+ button:disabled { cursor: wait; opacity: 0.7; }
37
+ pre {
38
+ background: #f3f4f6; border-radius: 8px; overflow: auto;
39
+ padding: 12px;
40
+ }
41
+ </style>
42
+ </head>
43
+ <body>
44
+ <h1>Reclaim demo verification</h1>
45
+ <p>Uses provider <code>${providerId}</code>.</p>
46
+ <button id="start">Start verification</button>
47
+ <pre id="status">Ready.</pre>
48
+ <p>Webhook endpoint: <code>/webhooks/reclaim</code></p>
49
+ <script>
50
+ const button = document.getElementById("start");
51
+ const status = document.getElementById("status");
52
+ button.addEventListener("click", async () => {
53
+ button.disabled = true;
54
+ status.textContent = "Creating verification request...";
55
+ try {
56
+ const response = await fetch("/api/reclaim/request", {
57
+ method: "POST",
58
+ });
59
+ if (!response.ok) throw new Error(await response.text());
60
+ const { requestUrl } = await response.json();
61
+ status.textContent = "Opening Reclaim verification...";
62
+ window.open(requestUrl, "_blank", "noopener,noreferrer");
63
+ status.textContent =
64
+ "Verification opened. Complete it in the Reclaim window.";
65
+ } catch (error) {
66
+ status.textContent =
67
+ error instanceof Error ? error.message : String(error);
68
+ button.disabled = false;
69
+ }
70
+ });
71
+ </script>
72
+ </body>
73
+ </html>`);
74
+ });
75
+
76
+ app.post("/api/reclaim/request", async (req, res, next) => {
77
+ try {
78
+ const appId = process.env.RECLAIM_APP_ID;
79
+ const appSecret = process.env.RECLAIM_APP_SECRET;
80
+ if (!appId || !appSecret) {
81
+ return res.status(500).json({
82
+ error: "Missing RECLAIM_APP_ID or RECLAIM_APP_SECRET",
83
+ });
84
+ }
85
+
86
+ const origin =
87
+ process.env.PUBLIC_URL || `${req.protocol}://${req.get("host")}`;
88
+ const reclaimProofRequest = await ReclaimProofRequest.init(
89
+ appId,
90
+ appSecret,
91
+ providerId,
92
+ { providerVersion },
93
+ );
94
+ reclaimProofRequest.setAppCallbackUrl(
95
+ `${origin}/webhooks/reclaim`,
96
+ true,
97
+ );
98
+ reclaimProofRequest.setContext(
99
+ randomUUID(),
100
+ JSON.stringify({ createdAt: new Date().toISOString() }),
101
+ );
102
+
103
+ // getRequestUrl() is ASYNC in the current SDK — must be awaited.
104
+ const requestUrl = await reclaimProofRequest.getRequestUrl();
105
+
106
+ res.json({
107
+ request: reclaimProofRequest.toJsonString(),
108
+ requestUrl,
109
+ });
110
+ } catch (error) {
111
+ next(error);
112
+ }
113
+ });
114
+
115
+ app.post("/webhooks/reclaim", async (req, res, next) => {
116
+ try {
117
+ // With jsonProofResponse: true + express.json() above, req.body is
118
+ // already a parsed Proof/Proof[]. Without either of those, the raw body
119
+ // is text and must be JSON.parse()'d (and URL-decoded if form-encoded)
120
+ // before calling verifyProof.
121
+ const proof = req.body;
122
+
123
+ // The second argument is REQUIRED — must include providerId.
124
+ const result = await verifyProof(proof, { providerId, providerVersion });
125
+
126
+ console.log("Received Reclaim verification:");
127
+ console.log(JSON.stringify(
128
+ { isVerified: result.isVerified, data: result.data, error: result.error },
129
+ null,
130
+ 2,
131
+ ));
132
+
133
+ res.status(200).json({ received: true, verified: result.isVerified });
134
+ } catch (error) {
135
+ next(error);
136
+ }
137
+ });
138
+
139
+ app.use((err, _req, res, _next) => {
140
+ console.error(err);
141
+ res.status(500).json({
142
+ error: err instanceof Error ? err.message : "Internal server error",
143
+ });
144
+ });
145
+
146
+ app.listen(port, () => {
147
+ console.log(`Reclaim demo server listening on http://localhost:${port}`);
148
+ console.log(`Verify page: http://localhost:${port}/verify`);
149
+ console.log(`Webhook endpoint: http://localhost:${port}/webhooks/reclaim`);
150
+ if (!process.env.PUBLIC_URL) {
151
+ console.log(
152
+ "Set PUBLIC_URL (e.g. an ngrok URL) so the webhook is reachable " +
153
+ "from Reclaim's servers.",
154
+ );
155
+ }
156
+ });
@@ -0,0 +1,119 @@
1
+ # @reclaimprotocol/js-sdk — backend integration guide
2
+
3
+ Keep `appSecret` on your BACKEND only. Everything below runs server-side;
4
+ the browser only ever sees the `requestUrl`.
5
+
6
+ ## Testing locally, without deploying anything
7
+
8
+ - **providerId `"example"`**: a canonical built-in smoke-test provider —
9
+ no need to author/publish your own provider just to confirm the SDK
10
+ plumbing works end-to-end. Swap in your real providerId once this passes.
11
+ - **A public callback URL**: `setAppCallbackUrl` needs a URL Reclaim's
12
+ servers can reach over the public internet — `localhost` will never
13
+ receive the callback. Two ways to get one, pick whichever fits:
14
+ - **ngrok** (or a similar tunnel): `ngrok http 3000` (or your port)
15
+ alongside your local server, then set `PUBLIC_URL` to the
16
+ `https://<id>.ngrok-free.app` URL it prints. Zero-deploy, but the tunnel
17
+ URL is temporary.
18
+ - **A dev/staging server you can deploy to quickly**: if you already have
19
+ one reachable from the internet, just point `PUBLIC_URL` at it instead —
20
+ skips the extra local tool.
21
+ Without either, only the frontend-side flow (opening `requestUrl` and
22
+ reading the result client-side) works — the async webhook never lands.
23
+ - `scaffold_reclaim_demo_server` defaults `providerId` to `"example"`; its
24
+ README covers both callback-URL options above — use it to get a working
25
+ local loop before wiring in a real provider.
26
+
27
+ ## 1. Install
28
+
29
+ ```bash
30
+ npm install @reclaimprotocol/js-sdk
31
+ ```
32
+
33
+ ## 2. Create a proof request (backend)
34
+
35
+ ```js
36
+ import { ReclaimProofRequest } from "@reclaimprotocol/js-sdk";
37
+
38
+ const reclaimProofRequest = await ReclaimProofRequest.init(
39
+ process.env.RECLAIM_APP_ID,
40
+ process.env.RECLAIM_APP_SECRET,
41
+ providerId,
42
+ {
43
+ // acceptAiProviders: true, // allow AI-verified providers
44
+ // useAppClip: true, // mobile app-clip flow, not a browser popup
45
+ // providerVersion: "1.0.0", // pin a specific provider version
46
+ },
47
+ );
48
+
49
+ // Deliver the verified proof back to your server asynchronously instead of
50
+ // (or in addition to) reading it from the frontend callback:
51
+ reclaimProofRequest.setAppCallbackUrl(
52
+ `${process.env.PUBLIC_URL}/webhooks/reclaim`,
53
+ true, // jsonProofResponse: send the callback body as JSON, not form-encoded
54
+ );
55
+
56
+ // Optional: attach your own correlation id / metadata, retrievable later.
57
+ reclaimProofRequest.setContext(
58
+ sessionId,
59
+ JSON.stringify({ createdAt: Date.now() }),
60
+ );
61
+
62
+ // IMPORTANT: getRequestUrl() is ASYNC — await it.
63
+ const requestUrl = await reclaimProofRequest.getRequestUrl();
64
+
65
+ // Hand the requestUrl to the frontend to open in a new tab/window/webview.
66
+ // If you need to rehydrate this request object later (e.g. in the webhook
67
+ // handler, in a different process), persist reclaimProofRequest.toJsonString()
68
+ // (sync) and rehydrate with:
69
+ // const req = await ReclaimProofRequest.fromJsonString(json); // ASYNC
70
+ ```
71
+
72
+ ## 3. Frontend: open the request URL
73
+
74
+ Fetch `requestUrl` from your backend, then open it (popup/new tab/QR code) —
75
+ never construct it in the browser, since that would require the appSecret
76
+ client-side. Trigger the fetch+open from a real user click (not a page-load
77
+ effect) or some browsers block the popup.
78
+
79
+ ## 4. Verify the callback (backend webhook)
80
+
81
+ ```js
82
+ import { verifyProof } from "@reclaimprotocol/js-sdk";
83
+
84
+ app.post("/webhooks/reclaim", express.json(), async (req, res) => {
85
+ // req.body is already a parsed object here because jsonProofResponse was
86
+ // true above AND express.json() parsed it. If you did NOT set
87
+ // jsonProofResponse (or aren't using express.json()), the body arrives as
88
+ // form-encoded/raw text and must be JSON.parse()'d (and decodeURIComponent'd
89
+ // if form-encoded) into a Proof / Proof[] before calling verifyProof.
90
+ const proof = req.body;
91
+
92
+ // The second argument is REQUIRED — at minimum providerId.
93
+ const result = await verifyProof(proof, {
94
+ providerId,
95
+ // providerVersion, // optional, pin if you pinned it in init()
96
+ });
97
+
98
+ if (!result.isVerified) { // NOTE: isVerified, not is_verified
99
+ console.error("Proof verification failed:", result.error);
100
+ return res.status(400).json({ verified: false, error: result.error });
101
+ }
102
+
103
+ console.log("Verified data:", result.data, result.publicData);
104
+ res.status(200).json({ verified: true });
105
+ });
106
+ ```
107
+
108
+ ## Gotchas
109
+
110
+ - `getRequestUrl()` and `ReclaimProofRequest.fromJsonString()` are async —
111
+ a missing `await` silently hands you a Promise instead of a string/instance.
112
+ - `verifyProof`'s config arg is mandatory; omitting `providerId` throws.
113
+ - Prefer `setContext` over the deprecated `addContext` (same signature).
114
+ - `ReclaimProofRequest.init` needs network access to the Reclaim backend at
115
+ call time — don't call it at module load / cold-start if your platform pools
116
+ processes long-term; call it per-request instead.
117
+ - Use `check_app_status` (this MCP server) to confirm your app is linked
118
+ and not still sandbox-limited before debugging "verification never lands"
119
+ issues.
@@ -28,42 +28,42 @@ export interface LaunchOptions {
28
28
  */
29
29
  function browserCandidates(): string[] {
30
30
  switch (platform()) {
31
- case 'darwin':
32
- return [
33
- '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
34
- join(
35
- homedir(),
36
- 'Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
37
- ),
38
- '/Applications/Chromium.app/Contents/MacOS/Chromium',
39
- '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
40
- ]
41
- case 'win32': {
42
- const appData =
31
+ case 'darwin':
32
+ return [
33
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
34
+ join(
35
+ homedir(),
36
+ 'Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
37
+ ),
38
+ '/Applications/Chromium.app/Contents/MacOS/Chromium',
39
+ '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
40
+ ]
41
+ case 'win32': {
42
+ const appData =
43
43
  localAppData() ?? join(homedir(), 'AppData', 'Local')
44
- return [
44
+ return [
45
45
  // System-wide Chrome
46
- 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
47
- 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
48
- // Per-user Chrome ("Install for me only")
49
- join(appData, 'Google', 'Chrome', 'Application', 'chrome.exe'),
50
- // Edge — Chromium-based, preinstalled on Win 10/11
51
- 'C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe',
52
- 'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe',
53
- ]
54
- }
46
+ 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
47
+ 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
48
+ // Per-user Chrome ("Install for me only")
49
+ join(appData, 'Google', 'Chrome', 'Application', 'chrome.exe'),
50
+ // Edge — Chromium-based, preinstalled on Win 10/11
51
+ 'C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe',
52
+ 'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe',
53
+ ]
54
+ }
55
55
 
56
- case 'linux':
57
- return [
58
- '/usr/bin/google-chrome',
59
- '/usr/bin/google-chrome-stable',
60
- '/usr/bin/chromium',
61
- '/usr/bin/chromium-browser',
62
- '/snap/bin/chromium',
63
- '/usr/bin/microsoft-edge',
64
- ]
65
- default:
66
- return []
56
+ case 'linux':
57
+ return [
58
+ '/usr/bin/google-chrome',
59
+ '/usr/bin/google-chrome-stable',
60
+ '/usr/bin/chromium',
61
+ '/usr/bin/chromium-browser',
62
+ '/snap/bin/chromium',
63
+ '/usr/bin/microsoft-edge',
64
+ ]
65
+ default:
66
+ return []
67
67
  }
68
68
  }
69
69
 
@@ -1,9 +1,8 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
2
+ import type { ReclaimClient } from '@reclaimprotocol/client'
2
3
  import type {
3
- ReclaimClient
4
- } from '@reclaimprotocol/client'
5
- import type {
6
- IOperationArgs, IOperationId
4
+ IOperationArgs,
5
+ IOperationId
7
6
  } from '@reclaimprotocol/client/openapi'
8
7
  import { remapErrorAsResponse } from '../remap_errors.ts'
9
8
  import { MCP_TOOLS } from './operations.gen.ts'
@@ -29,7 +29,7 @@ export const remapErrorAsResponse = (
29
29
  {
30
30
  type: 'text' as const,
31
31
  text: `${problemError.message}: ${problemError.data?.detail}`
32
- }
32
+ }
33
33
  ],
34
34
  }
35
35
  }
@@ -14,10 +14,42 @@ import {
14
14
  } from '../../../authoring/index.ts'
15
15
  import { runAttestorProof } from '../../../proof/attestor.ts'
16
16
  import { replayProvider } from '../../../proof/replay.ts'
17
+ import type { OprfMode, ReclaimProvider } from '../../../provider/schema.ts'
17
18
  import type { RegisteredTool } from '../../server.ts'
18
19
  import type { CaptureEntry, CaptureStore } from './capture.ts'
19
20
  import { shapeRichProofResult } from './proof.ts'
20
21
 
22
+ /**
23
+ * OPRF mode this LOCAL (non-TEE) attestor can actually run. Recipes carry
24
+ * `oprf-mpc` — the canonical/publish mode a TEE expects — but the local dev
25
+ * attestor can't do MPC. gnark `oprf` (Linux/Mac, needs ZK circuits) is the
26
+ * strongest it supports; Windows falls back to server-side `oprf-raw` (no
27
+ * circuits). Proving with the wrong mode fails, which is what pushed the agent
28
+ * to hand-craft a plaintext workaround.
29
+ */
30
+ const LOCAL_OPRF_MODE: OprfMode =
31
+ process.platform === 'win32' ? 'oprf-raw' : 'oprf'
32
+
33
+ /**
34
+ * Clone `provider` with every `oprf-mpc` redaction remapped to the mode this
35
+ * local attestor can run, so the PROOF validates locally. The draft itself is
36
+ * untouched — it keeps `oprf-mpc`, so the PUBLISHED recipe is always the
37
+ * TEE-canonical mode regardless of where it was proved.
38
+ */
39
+ function withLocalOprfMode(provider: ReclaimProvider): ReclaimProvider {
40
+ const reds = provider.responseRedactions
41
+ if(!reds?.some((r) => r.hash === 'oprf-mpc')) {
42
+ return provider
43
+ }
44
+
45
+ return {
46
+ ...provider,
47
+ responseRedactions: reds.map((r) => {
48
+ return r.hash === 'oprf-mpc' ? { ...r, hash: LOCAL_OPRF_MODE } : r
49
+ }),
50
+ }
51
+ }
52
+
21
53
  /** The local backend for one capture: drive its tab over CDP, read its
22
54
  * captured requests, prove via the in-process attestor, replay by re-fetch. */
23
55
  function localBackend(entry: CaptureEntry): AuthoringBackend {
@@ -64,8 +96,10 @@ function localBackend(entry: CaptureEntry): AuthoringBackend {
64
96
  }
65
97
  },
66
98
  prove: async(draft, opts) => {
99
+ // Prove with the locally-runnable OPRF mode; the draft (→ published
100
+ // recipe) keeps oprf-mpc.
67
101
  const result = await runAttestorProof(
68
- draft.provider,
102
+ withLocalOprfMode(draft.provider),
69
103
  draft.secrets,
70
104
  {
71
105
  ...(opts?.ownerAddress ? { ownerAddress: opts.ownerAddress } : {}),
@@ -1,7 +1,5 @@
1
1
  import type { ReclaimClient } from '@reclaimprotocol/client'
2
- import type {
3
- CredentialAttachSession
4
- } from '@reclaimprotocol/client/openapi'
2
+ import type { CredentialAttachSession } from '@reclaimprotocol/client/openapi'
5
3
  import { defineTool, type RegisteredTool } from '../../server.ts'
6
4
  import { writeSession } from './session-cache.ts'
7
5
 
@@ -11,10 +11,7 @@
11
11
  * The hex is loaded via the client's `loadDecryptionKey` and passed straight
12
12
  * through as `ReclaimConfig.credential` / to `decryptCallback`.
13
13
  */
14
- import {
15
- type DecryptionKey,
16
- loadDecryptionKey,
17
- } from '@reclaimprotocol/client'
14
+ import { type DecryptionKey, loadDecryptionKey } from '@reclaimprotocol/client'
18
15
  import { existsSync, readFileSync } from 'node:fs'
19
16
  import { join } from 'node:path'
20
17
  import { findProjectRoot } from '../../../paths.ts'
@@ -9,7 +9,9 @@ import type { ReclaimOldClient } from '../client.ts'
9
9
  * and sandboxed (`sandboxLimit: 100` sessions). Use `link_app_to_account` to
10
10
  * attach it to your dashboard account, or `check_app_status` to inspect it.
11
11
  */
12
- export function issueAppCredentialsTool(client: ReclaimOldClient): RegisteredTool {
12
+ export function issueAppCredentialsTool(
13
+ client: ReclaimOldClient,
14
+ ): RegisteredTool {
13
15
  return defineTool<Record<string, never>>(
14
16
  {
15
17
  name: 'issue_app_credentials',