@juspay/neurolink 11.29.2 → 11.30.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.
Files changed (50) hide show
  1. package/CHANGELOG.md +3 -3
  2. package/dist/auth/anthropicOAuth.d.ts +50 -0
  3. package/dist/auth/anthropicOAuth.js +78 -0
  4. package/dist/browser/neurolink.min.js +393 -393
  5. package/dist/cli/commands/proxy.d.ts +2 -0
  6. package/dist/cli/commands/proxy.js +284 -4
  7. package/dist/cli/commands/proxyExpose.d.ts +35 -0
  8. package/dist/cli/commands/proxyExpose.js +252 -0
  9. package/dist/cli/commands/proxyPeer.d.ts +29 -0
  10. package/dist/cli/commands/proxyPeer.js +738 -0
  11. package/dist/cli/commands/proxyShare.d.ts +37 -0
  12. package/dist/cli/commands/proxyShare.js +1080 -0
  13. package/dist/cli/parser.js +7 -1
  14. package/dist/proxy/peerStore.d.ts +52 -0
  15. package/dist/proxy/peerStore.js +324 -0
  16. package/dist/proxy/peerTransport.d.ts +38 -0
  17. package/dist/proxy/peerTransport.js +242 -0
  18. package/dist/proxy/proxyPaths.d.ts +8 -0
  19. package/dist/proxy/proxyPaths.js +55 -17
  20. package/dist/proxy/requestLogger.js +8 -0
  21. package/dist/proxy/residentGrants.d.ts +57 -0
  22. package/dist/proxy/residentGrants.js +393 -0
  23. package/dist/proxy/shareAudit.d.ts +81 -0
  24. package/dist/proxy/shareAudit.js +280 -0
  25. package/dist/proxy/shareContext.d.ts +38 -0
  26. package/dist/proxy/shareContext.js +92 -0
  27. package/dist/proxy/shareGate.d.ts +64 -0
  28. package/dist/proxy/shareGate.js +216 -0
  29. package/dist/proxy/shareGrants.d.ts +115 -0
  30. package/dist/proxy/shareGrants.js +590 -0
  31. package/dist/proxy/shareLease.d.ts +101 -0
  32. package/dist/proxy/shareLease.js +192 -0
  33. package/dist/proxy/shareLedger.d.ts +105 -0
  34. package/dist/proxy/shareLedger.js +406 -0
  35. package/dist/proxy/shareListener.d.ts +60 -0
  36. package/dist/proxy/shareListener.js +143 -0
  37. package/dist/proxy/shareNotes.d.ts +97 -0
  38. package/dist/proxy/shareNotes.js +234 -0
  39. package/dist/proxy/sharePolicy.d.ts +110 -0
  40. package/dist/proxy/sharePolicy.js +366 -0
  41. package/dist/proxy/shareProvisioning.d.ts +110 -0
  42. package/dist/proxy/shareProvisioning.js +237 -0
  43. package/dist/proxy/shareReceipts.d.ts +99 -0
  44. package/dist/proxy/shareReceipts.js +303 -0
  45. package/dist/proxy/shareSigning.d.ts +40 -0
  46. package/dist/proxy/shareSigning.js +78 -0
  47. package/dist/server/routes/claudeProxyRoutes.js +1066 -3
  48. package/dist/types/cli.d.ts +61 -0
  49. package/dist/types/proxy.d.ts +781 -0
  50. package/package.json +2 -1
@@ -0,0 +1,252 @@
1
+ /**
2
+ * `neurolink proxy expose` — put this node's proxy on a public URL.
3
+ *
4
+ * Wraps `cloudflared` because that is the shortest path from a laptop to a URL
5
+ * a peer can reach, with no port forwarding and no inbound firewall change.
6
+ *
7
+ * **The safety check is the point.** Whether a port is gated depends on the
8
+ * proxy process — which listener it is, and how that process was started —
9
+ * none of which this command can read. So instead of trusting configuration, it
10
+ * asks the running proxy directly: a request with no share token must be
11
+ * refused. A port that answers one is open, and exposing it would publish the
12
+ * operator's subscription to anyone who finds the URL, so the tunnel is refused
13
+ * rather than opened.
14
+ *
15
+ * With no `--port` it targets the gate-only share listener, which is the port
16
+ * that exists to face outward.
17
+ *
18
+ * @module cli/commands/proxyExpose
19
+ */
20
+ import { spawn } from "node:child_process";
21
+ /** Cloudflare prints the assigned hostname to stderr as it comes up. */
22
+ const QUICK_TUNNEL_URL = /https:\/\/[a-z0-9-]+\.trycloudflare\.com/i;
23
+ /**
24
+ * Ask the running proxy whether it refuses untokened traffic.
25
+ *
26
+ * A refusal carrying `x-neurolink-grant-reason: missing_token` is proof the
27
+ * gate is live. Anything else — an answer, an upstream error, a credentials
28
+ * complaint — means the request got past the gate, which is the dangerous case.
29
+ *
30
+ * `scheme` matters: probing a TLS address over plain http fails to connect,
31
+ * which reads as unreachable, and an unreachable address is reported as *not*
32
+ * dangerous. A public `https://` URL would therefore never raise the warning it
33
+ * exists to raise.
34
+ */
35
+ export async function probeProxyGate(host, port, scheme = "http") {
36
+ const controller = new AbortController();
37
+ const timeout = setTimeout(() => controller.abort(), 10_000);
38
+ try {
39
+ const response = await fetch(`${scheme}://${host}:${port}/v1/messages`, {
40
+ method: "POST",
41
+ headers: { "content-type": "application/json" },
42
+ body: JSON.stringify({
43
+ model: "neurolink-gate-probe",
44
+ max_tokens: 1,
45
+ messages: [{ role: "user", content: "probe" }],
46
+ }),
47
+ signal: controller.signal,
48
+ });
49
+ await response.text().catch(() => "");
50
+ const reason = response.headers.get("x-neurolink-grant-reason");
51
+ if (reason === "missing_token") {
52
+ return {
53
+ gated: true,
54
+ reachable: true,
55
+ detail: "the proxy refuses requests without a share token",
56
+ };
57
+ }
58
+ return {
59
+ gated: false,
60
+ reachable: true,
61
+ detail: "the proxy served a request that carried no share token — anyone who reaches the tunnel could spend your subscription",
62
+ };
63
+ }
64
+ catch (error) {
65
+ return {
66
+ gated: false,
67
+ reachable: false,
68
+ detail: `could not reach the proxy: ${error instanceof Error ? error.message : String(error)}`,
69
+ };
70
+ }
71
+ finally {
72
+ clearTimeout(timeout);
73
+ }
74
+ }
75
+ /**
76
+ * Which port to expose.
77
+ *
78
+ * The gate-only share listener when the running proxy has one, because that is
79
+ * the port whose whole purpose is to face outward. An explicit `--port` always
80
+ * wins — an operator pointing at something specific is not to be second-guessed.
81
+ */
82
+ async function resolveExposePort(argv) {
83
+ if (argv.port !== undefined) {
84
+ return { port: argv.port, isShareListener: false };
85
+ }
86
+ try {
87
+ const { StateFileManager } = await import("../utils/serverUtils.js");
88
+ const state = new StateFileManager("proxy-state.json").load();
89
+ if (state?.sharePort) {
90
+ return { port: state.sharePort, isShareListener: true };
91
+ }
92
+ if (state?.port) {
93
+ return { port: state.port, isShareListener: false };
94
+ }
95
+ }
96
+ catch {
97
+ // No readable state — fall through to the historical default.
98
+ }
99
+ return { port: 3000, isShareListener: false };
100
+ }
101
+ async function runExpose(argv) {
102
+ const host = argv.host ?? "127.0.0.1";
103
+ const resolved = await resolveExposePort(argv);
104
+ const port = resolved.port;
105
+ if (resolved.isShareListener) {
106
+ console.info(`Exposing the share listener on port ${port} — your own client keeps using the main port.`);
107
+ }
108
+ const probe = await probeProxyGate(host, port);
109
+ if (!probe.reachable) {
110
+ throw new Error(resolved.isShareListener
111
+ ? `${probe.detail}\nThe share listener runs only while an active grant exists. Issue one first:\n neurolink proxy share create --peer <name> --preset spare`
112
+ : `${probe.detail}\nStart it first: neurolink proxy start --port ${port}`);
113
+ }
114
+ if (!probe.gated && !argv.force) {
115
+ throw new Error([
116
+ `Refusing to expose an ungated proxy: ${probe.detail}.`,
117
+ "",
118
+ "Issue a grant, which brings up the gate-only share listener, and",
119
+ "expose that instead:",
120
+ " neurolink proxy share create --peer <name> --preset spare",
121
+ " neurolink proxy expose",
122
+ "",
123
+ "Or gate this port itself, which also refuses your own local client:",
124
+ ` NEUROLINK_PROXY_REQUIRE_GRANT=1 neurolink proxy start --port ${port}`,
125
+ "",
126
+ "Pass --force only if something in front of the tunnel is already",
127
+ "authenticating every request.",
128
+ ].join("\n"));
129
+ }
130
+ if (!probe.gated && argv.force) {
131
+ console.warn("⚠ Exposing an ungated proxy because --force was given. Every request that reaches the tunnel will be served.");
132
+ }
133
+ const args = argv.named
134
+ ? ["tunnel", "run", argv.named]
135
+ : ["tunnel", "--url", `http://${host}:${port}`];
136
+ if (argv.named) {
137
+ // Say plainly what the check above did and did not establish. A quick
138
+ // tunnel is pointed at `http://host:port` on the line below, so probing
139
+ // that address settles what the tunnel will front. A named tunnel is not:
140
+ // its ingress lives in cloudflared's own configuration, and it may route
141
+ // to the main ungated port — or to something else entirely — no matter
142
+ // what this command just probed. The gate result is still worth having,
143
+ // but it describes a local port, not this tunnel.
144
+ console.warn("");
145
+ console.warn(`⚠ The gate check covered http://${host}:${port}. A named tunnel's ingress is`);
146
+ console.warn(` configured in cloudflared, not here, so confirm ${argv.named} actually points at`);
147
+ console.warn(" that port — routing it at an ungated port exposes the pool regardless of the");
148
+ console.warn(" result above.");
149
+ console.warn("");
150
+ }
151
+ console.info(`Starting cloudflared: cloudflared ${args.join(" ")}`);
152
+ const child = spawn("cloudflared", args, {
153
+ stdio: ["ignore", "pipe", "pipe"],
154
+ });
155
+ let announced = false;
156
+ const announce = (chunk) => {
157
+ if (announced) {
158
+ return;
159
+ }
160
+ const match = QUICK_TUNNEL_URL.exec(chunk);
161
+ if (!match) {
162
+ return;
163
+ }
164
+ announced = true;
165
+ const url = match[0];
166
+ console.info("");
167
+ console.info(` Public URL: ${url}`);
168
+ console.info("");
169
+ console.info(" Mint a token and hand the peer a link:");
170
+ console.info(` neurolink proxy share create --peer <name> --preset spare --public-url ${url}`);
171
+ console.info("");
172
+ console.info(" A quick tunnel's URL changes every restart. For a peer you expect to");
173
+ console.info(" keep, use a named tunnel so their configuration does not go stale:");
174
+ console.info(" neurolink proxy expose --named <tunnel-name>");
175
+ };
176
+ child.stdout?.on("data", (chunk) => {
177
+ const text = chunk.toString();
178
+ announce(text);
179
+ process.stdout.write(text);
180
+ });
181
+ child.stderr?.on("data", (chunk) => {
182
+ const text = chunk.toString();
183
+ announce(text);
184
+ process.stderr.write(text);
185
+ });
186
+ child.on("error", (error) => {
187
+ if (error.code === "ENOENT") {
188
+ console.error("cloudflared is not installed. See https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/");
189
+ process.exitCode = 1;
190
+ return;
191
+ }
192
+ console.error(error.message);
193
+ process.exitCode = 1;
194
+ });
195
+ // `close` rather than `exit`: a spawn that never started — cloudflared not on
196
+ // PATH — emits `error` and `close` but no `exit`, and waiting on `exit` alone
197
+ // left the command hanging forever on the one failure it explains best.
198
+ await new Promise((resolve) => {
199
+ let settled = false;
200
+ const settle = () => {
201
+ if (settled) {
202
+ return;
203
+ }
204
+ settled = true;
205
+ resolve();
206
+ };
207
+ child.on("close", (code) => {
208
+ if (code !== 0) {
209
+ process.exitCode = code ?? 1;
210
+ }
211
+ settle();
212
+ });
213
+ child.on("error", settle);
214
+ });
215
+ }
216
+ export const proxyExposeCommand = {
217
+ command: "expose",
218
+ describe: "Publish this node's proxy through a cloudflared tunnel",
219
+ builder: (yargs) => yargs
220
+ .option("port", {
221
+ type: "number",
222
+ // Deliberately no default. `resolveExposePort` reads the running
223
+ // proxy's state file to find the gate-only share listener, and it can
224
+ // only do that when `--port` is genuinely absent — a yargs default
225
+ // makes every invocation look explicit and pins the tunnel to 3000.
226
+ description: "Local proxy port to expose (default: the share listener, else the running proxy's port)",
227
+ })
228
+ .option("host", {
229
+ type: "string",
230
+ default: "127.0.0.1",
231
+ description: "Local proxy host",
232
+ })
233
+ .option("named", {
234
+ type: "string",
235
+ description: "Run a pre-created named tunnel instead of a quick tunnel (stable URL)",
236
+ })
237
+ .option("force", {
238
+ type: "boolean",
239
+ default: false,
240
+ description: "Expose even though the proxy serves untokened requests (dangerous)",
241
+ }),
242
+ handler: async (argv) => {
243
+ try {
244
+ await runExpose(argv);
245
+ }
246
+ catch (error) {
247
+ console.error(error instanceof Error ? error.message : String(error));
248
+ process.exitCode = 1;
249
+ }
250
+ },
251
+ };
252
+ //# sourceMappingURL=proxyExpose.js.map
@@ -0,0 +1,29 @@
1
+ /**
2
+ * `neurolink proxy peer` — the borrower's controls.
3
+ *
4
+ * A peer is a lender's exposed proxy plus the share token they issued. Peers are
5
+ * only consulted after every local account is spent, so adding one can never
6
+ * make this node spend someone else's capacity while it still has its own.
7
+ *
8
+ * @module cli/commands/proxyPeer
9
+ */
10
+ import type { CommandModule } from "yargs";
11
+ import type { ProxyPeerArgs } from "../../types/index.js";
12
+ /**
13
+ * Parse a share link into its parts.
14
+ *
15
+ * The token rides in the fragment so it is never sent to whatever host resolves
16
+ * the URL — fragments are not transmitted.
17
+ * Shape: `neurolink://share/<host>[/<path>]#<token>`
18
+ *
19
+ * Everything between `share/` and the fragment is the lender's address,
20
+ * **including any path**. A lender fronted at `example.com/proxy` is an ordinary
21
+ * reverse-proxy layout, and dropping the path silently produced a peer URL
22
+ * nothing answers on.
23
+ */
24
+ export declare function parseShareLink(link: string): {
25
+ url: string;
26
+ token: string;
27
+ receiptSecret?: string;
28
+ } | undefined;
29
+ export declare const proxyPeerCommand: CommandModule<object, ProxyPeerArgs>;