blue-js-sdk 2.0.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 (215) hide show
  1. package/CHANGELOG.md +446 -0
  2. package/LICENSE +21 -0
  3. package/README.md +75 -0
  4. package/ai-path/ADMIN-ELEVATION.md +116 -0
  5. package/ai-path/AI-MANIFESTO.md +185 -0
  6. package/ai-path/BREAKING.md +74 -0
  7. package/ai-path/CHECKLIST.md +619 -0
  8. package/ai-path/CONNECTION-STEPS.md +724 -0
  9. package/ai-path/DECISION-TREE.md +378 -0
  10. package/ai-path/DEPENDENCIES.md +459 -0
  11. package/ai-path/E2E-FLOW.md +1555 -0
  12. package/ai-path/FAILURES.md +403 -0
  13. package/ai-path/GUIDE.md +1217 -0
  14. package/ai-path/README.md +558 -0
  15. package/ai-path/SPLIT-TUNNEL.md +266 -0
  16. package/ai-path/cli.js +535 -0
  17. package/ai-path/connect.js +884 -0
  18. package/ai-path/discover.js +178 -0
  19. package/ai-path/environment.js +266 -0
  20. package/ai-path/errors.js +86 -0
  21. package/ai-path/examples/autonomous-agent.mjs +220 -0
  22. package/ai-path/examples/multi-region.mjs +174 -0
  23. package/ai-path/examples/one-shot.mjs +31 -0
  24. package/ai-path/index.js +60 -0
  25. package/ai-path/pricing.js +136 -0
  26. package/ai-path/recommend.js +413 -0
  27. package/ai-path/run-admin.vbs +25 -0
  28. package/ai-path/setup.js +291 -0
  29. package/ai-path/wallet.js +137 -0
  30. package/app-helpers.js +363 -0
  31. package/app-settings.js +95 -0
  32. package/app-types.js +267 -0
  33. package/audit.js +847 -0
  34. package/batch.js +293 -0
  35. package/bin/setup.js +376 -0
  36. package/chain/authz.js +109 -0
  37. package/chain/broadcast.js +472 -0
  38. package/chain/client.js +160 -0
  39. package/chain/fee-grants.js +305 -0
  40. package/chain/index.js +891 -0
  41. package/chain/lcd.js +313 -0
  42. package/chain/queries.js +547 -0
  43. package/chain/rpc.js +408 -0
  44. package/chain/wallet.js +141 -0
  45. package/cli/config.js +143 -0
  46. package/cli/index.js +463 -0
  47. package/cli/output.js +182 -0
  48. package/cli.js +491 -0
  49. package/client/index.js +251 -0
  50. package/client.js +271 -0
  51. package/config/index.js +255 -0
  52. package/connection/connect.js +849 -0
  53. package/connection/disconnect.js +180 -0
  54. package/connection/discovery.js +321 -0
  55. package/connection/index.js +76 -0
  56. package/connection/proxy.js +148 -0
  57. package/connection/resilience.js +428 -0
  58. package/connection/security.js +232 -0
  59. package/connection/state.js +369 -0
  60. package/connection/tunnel.js +691 -0
  61. package/consumer.js +132 -0
  62. package/cosmjs-setup.js +1884 -0
  63. package/defaults.js +366 -0
  64. package/disk-cache.js +107 -0
  65. package/dist/client.d.ts +108 -0
  66. package/dist/client.d.ts.map +1 -0
  67. package/dist/client.js +400 -0
  68. package/dist/client.js.map +1 -0
  69. package/dist/index.d.ts +8 -0
  70. package/dist/index.d.ts.map +1 -0
  71. package/dist/index.js +8 -0
  72. package/dist/index.js.map +1 -0
  73. package/errors/index.js +112 -0
  74. package/errors.js +218 -0
  75. package/examples/README.md +64 -0
  76. package/examples/connect-direct.mjs +106 -0
  77. package/examples/connect-plan.mjs +125 -0
  78. package/examples/error-handling.mjs +109 -0
  79. package/examples/query-nodes.mjs +94 -0
  80. package/examples/wallet-basics.mjs +61 -0
  81. package/generated/amino/amino.ts +9 -0
  82. package/generated/cosmos/base/v1beta1/coin.ts +365 -0
  83. package/generated/cosmos_proto/cosmos.ts +323 -0
  84. package/generated/gogoproto/gogo.ts +9 -0
  85. package/generated/google/protobuf/descriptor.ts +7601 -0
  86. package/generated/google/protobuf/duration.ts +208 -0
  87. package/generated/google/protobuf/timestamp.ts +238 -0
  88. package/generated/sentinel/lease/v1/events.ts +924 -0
  89. package/generated/sentinel/lease/v1/lease.ts +292 -0
  90. package/generated/sentinel/lease/v1/msg.ts +949 -0
  91. package/generated/sentinel/lease/v1/params.ts +164 -0
  92. package/generated/sentinel/node/v3/events.ts +881 -0
  93. package/generated/sentinel/node/v3/msg.ts +1002 -0
  94. package/generated/sentinel/node/v3/node.ts +263 -0
  95. package/generated/sentinel/node/v3/params.ts +183 -0
  96. package/generated/sentinel/plan/v3/events.ts +675 -0
  97. package/generated/sentinel/plan/v3/msg.ts +1191 -0
  98. package/generated/sentinel/plan/v3/plan.ts +283 -0
  99. package/generated/sentinel/provider/v2/events.ts +171 -0
  100. package/generated/sentinel/provider/v2/msg.ts +480 -0
  101. package/generated/sentinel/provider/v2/params.ts +131 -0
  102. package/generated/sentinel/provider/v2/provider.ts +246 -0
  103. package/generated/sentinel/session/v3/events.ts +480 -0
  104. package/generated/sentinel/session/v3/msg.ts +616 -0
  105. package/generated/sentinel/session/v3/params.ts +260 -0
  106. package/generated/sentinel/session/v3/proof.ts +180 -0
  107. package/generated/sentinel/session/v3/session.ts +384 -0
  108. package/generated/sentinel/subscription/v3/events.ts +1181 -0
  109. package/generated/sentinel/subscription/v3/msg.ts +1305 -0
  110. package/generated/sentinel/subscription/v3/params.ts +167 -0
  111. package/generated/sentinel/subscription/v3/subscription.ts +315 -0
  112. package/generated/sentinel/types/v1/bandwidth.ts +124 -0
  113. package/generated/sentinel/types/v1/price.ts +149 -0
  114. package/generated/sentinel/types/v1/renewal.ts +87 -0
  115. package/generated/sentinel/types/v1/status.ts +54 -0
  116. package/generated/typeRegistry.ts +27 -0
  117. package/index.js +486 -0
  118. package/node-connect.js +3015 -0
  119. package/operator.js +134 -0
  120. package/package.json +113 -0
  121. package/plan-operations.js +199 -0
  122. package/preflight.js +352 -0
  123. package/pricing/index.js +262 -0
  124. package/proto/amino/amino.proto +84 -0
  125. package/proto/cosmos/base/v1beta1/coin.proto +61 -0
  126. package/proto/cosmos_proto/cosmos.proto +112 -0
  127. package/proto/gogoproto/gogo.proto +145 -0
  128. package/proto/google/api/annotations.proto +31 -0
  129. package/proto/google/api/http.proto +370 -0
  130. package/proto/google/protobuf/any.proto +106 -0
  131. package/proto/google/protobuf/duration.proto +115 -0
  132. package/proto/google/protobuf/timestamp.proto +145 -0
  133. package/proto/sentinel/lease/v1/events.proto +52 -0
  134. package/proto/sentinel/lease/v1/genesis.proto +15 -0
  135. package/proto/sentinel/lease/v1/lease.proto +25 -0
  136. package/proto/sentinel/lease/v1/msg.proto +62 -0
  137. package/proto/sentinel/lease/v1/params.proto +17 -0
  138. package/proto/sentinel/node/v3/events.proto +50 -0
  139. package/proto/sentinel/node/v3/genesis.proto +15 -0
  140. package/proto/sentinel/node/v3/msg.proto +63 -0
  141. package/proto/sentinel/node/v3/node.proto +27 -0
  142. package/proto/sentinel/node/v3/params.proto +21 -0
  143. package/proto/sentinel/node/v3/querier.proto +63 -0
  144. package/proto/sentinel/plan/v3/events.proto +41 -0
  145. package/proto/sentinel/plan/v3/genesis.proto +21 -0
  146. package/proto/sentinel/plan/v3/msg.proto +83 -0
  147. package/proto/sentinel/plan/v3/plan.proto +32 -0
  148. package/proto/sentinel/plan/v3/querier.proto +53 -0
  149. package/proto/sentinel/provider/v2/events.proto +16 -0
  150. package/proto/sentinel/provider/v2/genesis.proto +15 -0
  151. package/proto/sentinel/provider/v2/msg.proto +35 -0
  152. package/proto/sentinel/provider/v2/params.proto +17 -0
  153. package/proto/sentinel/provider/v2/provider.proto +24 -0
  154. package/proto/sentinel/provider/v3/genesis.proto +15 -0
  155. package/proto/sentinel/provider/v3/params.proto +13 -0
  156. package/proto/sentinel/session/v3/events.proto +30 -0
  157. package/proto/sentinel/session/v3/genesis.proto +15 -0
  158. package/proto/sentinel/session/v3/msg.proto +50 -0
  159. package/proto/sentinel/session/v3/params.proto +25 -0
  160. package/proto/sentinel/session/v3/proof.proto +25 -0
  161. package/proto/sentinel/session/v3/querier.proto +100 -0
  162. package/proto/sentinel/session/v3/session.proto +50 -0
  163. package/proto/sentinel/subscription/v2/allocation.proto +21 -0
  164. package/proto/sentinel/subscription/v2/payout.proto +22 -0
  165. package/proto/sentinel/subscription/v3/events.proto +65 -0
  166. package/proto/sentinel/subscription/v3/genesis.proto +17 -0
  167. package/proto/sentinel/subscription/v3/msg.proto +83 -0
  168. package/proto/sentinel/subscription/v3/params.proto +21 -0
  169. package/proto/sentinel/subscription/v3/subscription.proto +33 -0
  170. package/proto/sentinel/types/v1/bandwidth.proto +19 -0
  171. package/proto/sentinel/types/v1/price.proto +21 -0
  172. package/proto/sentinel/types/v1/renewal.proto +21 -0
  173. package/proto/sentinel/types/v1/status.proto +16 -0
  174. package/protocol/encoding.js +341 -0
  175. package/protocol/events.js +361 -0
  176. package/protocol/handshake.js +297 -0
  177. package/protocol/index.js +15 -0
  178. package/protocol/messages.js +346 -0
  179. package/protocol/plans.js +199 -0
  180. package/protocol/v2ray.js +268 -0
  181. package/protocol/v3.js +723 -0
  182. package/protocol/wireguard.js +125 -0
  183. package/security/index.js +132 -0
  184. package/session-manager.js +329 -0
  185. package/session-tracker.js +80 -0
  186. package/setup.js +376 -0
  187. package/speedtest/index.js +528 -0
  188. package/speedtest.js +567 -0
  189. package/src/client.ts +502 -0
  190. package/src/index.ts +20 -0
  191. package/state/index.js +347 -0
  192. package/state.js +516 -0
  193. package/test-all-chain-ops.js +493 -0
  194. package/test-all-logic.js +199 -0
  195. package/test-all-msg-types.js +292 -0
  196. package/test-every-connection.js +208 -0
  197. package/test-feegrant-connect.js +98 -0
  198. package/test-logic.js +148 -0
  199. package/test-mainnet.js +176 -0
  200. package/test-plan-lifecycle.js +335 -0
  201. package/tls-trust.js +132 -0
  202. package/tsconfig.build.json +20 -0
  203. package/tsconfig.json +34 -0
  204. package/types/chain.d.ts +746 -0
  205. package/types/connection.d.ts +425 -0
  206. package/types/errors.d.ts +174 -0
  207. package/types/index.d.ts +1380 -0
  208. package/types/nodes.d.ts +187 -0
  209. package/types/pricing.d.ts +156 -0
  210. package/types/protocol.d.ts +332 -0
  211. package/types/session.d.ts +236 -0
  212. package/types/settings.d.ts +192 -0
  213. package/v3protocol.js +1053 -0
  214. package/wallet/index.js +153 -0
  215. package/wireguard.js +307 -0
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Sentinel AI Path — Node Discovery & Selection
3
+ *
4
+ * An AI agent uses these functions to find the best node for its needs:
5
+ * budget, location, protocol, speed.
6
+ */
7
+
8
+ import {
9
+ queryOnlineNodes,
10
+ fetchActiveNodes,
11
+ filterNodes,
12
+ getNodePrices,
13
+ getNetworkOverview,
14
+ groupNodesByCountry,
15
+ formatP2P,
16
+ TRANSPORT_SUCCESS_RATES,
17
+ LCD_ENDPOINTS,
18
+ tryWithFallback,
19
+ // v1.5.0: RPC queries (protobuf via ABCI, ~10x faster than LCD for node lists)
20
+ createRpcQueryClientWithFallback,
21
+ rpcQueryNodes,
22
+ } from '../index.js';
23
+
24
+ // ─── discoverNodes() ─────────────────────────────────────────────────────────
25
+
26
+ /**
27
+ * Discover all available nodes on the Sentinel network.
28
+ * Returns every active node with address, protocol, pricing, and remote URL.
29
+ *
30
+ * By default queries the blockchain directly (fast, returns ALL nodes).
31
+ * Set opts.probe = true to individually probe each node for live status.
32
+ *
33
+ * @param {object} [opts]
34
+ * @param {string} [opts.country] - Filter by country name or code (e.g. 'Germany', 'DE')
35
+ * @param {string} [opts.protocol] - Filter by protocol: 'wireguard' or 'v2ray'
36
+ * @param {number} [opts.maxPrice] - Max price in udvpn per GB (filter expensive nodes)
37
+ * @param {number} [opts.limit] - Max nodes to return (default: all)
38
+ * @param {boolean} [opts.probe] - If true, probe each node individually for live status (slow). Default: false
39
+ * @param {function} [opts.onProgress] - Progress callback: ({ total, probed, online }) => void
40
+ * @returns {Promise<Array<{
41
+ * address: string,
42
+ * country: string|null,
43
+ * protocol: string,
44
+ * pricePerGb: { udvpn: number, p2p: string }|null,
45
+ * pricePerHour: { udvpn: number, p2p: string }|null,
46
+ * score: number,
47
+ * peers: number,
48
+ * remoteUrl: string,
49
+ * }>>}
50
+ */
51
+ export async function discoverNodes(opts = {}) {
52
+ if (opts && typeof opts !== 'object') {
53
+ throw new Error('discoverNodes(): opts must be an object or undefined');
54
+ }
55
+
56
+ let nodes;
57
+
58
+ if (opts.probe) {
59
+ // Slow path: probe each node for live status, peers, location
60
+ const maxNodes = opts.limit ? opts.limit * 3 : 3000;
61
+ nodes = await queryOnlineNodes({
62
+ maxNodes,
63
+ onNodeProbed: opts.onProgress || undefined,
64
+ });
65
+ } else {
66
+ // Fast path: query blockchain for all active nodes
67
+ // v1.5.0: Try RPC first (protobuf, ~10x faster), fall back to LCD
68
+ try {
69
+ const rpcClient = await createRpcQueryClientWithFallback();
70
+ nodes = await rpcQueryNodes(rpcClient, { status: 1, limit: 5000 });
71
+ } catch {
72
+ // RPC failed — fall back to LCD
73
+ const { result } = await tryWithFallback(
74
+ LCD_ENDPOINTS,
75
+ fetchActiveNodes,
76
+ 'discoverNodes',
77
+ );
78
+ nodes = result;
79
+ }
80
+ }
81
+
82
+ // Apply filters
83
+ if (opts.country) {
84
+ nodes = filterNodes(nodes, { country: opts.country });
85
+ }
86
+ if (opts.protocol) {
87
+ nodes = nodes.filter(n => {
88
+ const type = String(n.serviceType || n.service_type || '').toLowerCase();
89
+ return type === opts.protocol;
90
+ });
91
+ }
92
+
93
+ // Enrich with clean structure
94
+ const enriched = nodes.map(n => {
95
+ const gbPrices = n.gigabyte_prices || [];
96
+ const hrPrices = n.hourly_prices || [];
97
+ const udvpnGb = gbPrices.find(p => p.denom === 'udvpn');
98
+ const udvpnHr = hrPrices.find(p => p.denom === 'udvpn');
99
+
100
+ const pricePerGb = udvpnGb ? {
101
+ udvpn: parseInt(udvpnGb.quote_value || udvpnGb.amount || '0', 10),
102
+ p2p: formatP2P(parseInt(udvpnGb.quote_value || udvpnGb.amount || '0', 10)),
103
+ } : null;
104
+
105
+ const pricePerHour = udvpnHr ? {
106
+ udvpn: parseInt(udvpnHr.quote_value || udvpnHr.amount || '0', 10),
107
+ p2p: formatP2P(parseInt(udvpnHr.quote_value || udvpnHr.amount || '0', 10)),
108
+ } : null;
109
+
110
+ return {
111
+ address: n.address || n.acc_address,
112
+ country: n.country || n.location?.country || null,
113
+ protocol: (() => {
114
+ const t = String(n.serviceType || n.service_type || '').toLowerCase();
115
+ return t === 'wireguard' || t === 'v2ray' ? t : null;
116
+ })(),
117
+ pricePerGb,
118
+ pricePerHour,
119
+ score: n.qualityScore ?? n.score ?? 0,
120
+ peers: n.peers ?? null,
121
+ remoteUrl: n.remote_url || n.remote_addrs?.[0] || '',
122
+ };
123
+ });
124
+
125
+ // Filter by max price
126
+ if (opts.maxPrice && opts.maxPrice > 0) {
127
+ const filtered = enriched.filter(n =>
128
+ n.pricePerGb && n.pricePerGb.udvpn <= opts.maxPrice
129
+ );
130
+ if (filtered.length > 0) {
131
+ const sorted = filtered.sort((a, b) => (b.score || 0) - (a.score || 0));
132
+ return opts.limit ? sorted.slice(0, opts.limit) : sorted;
133
+ }
134
+ }
135
+
136
+ // Sort by score descending
137
+ const sorted = enriched.sort((a, b) => (b.score || 0) - (a.score || 0));
138
+ return opts.limit ? sorted.slice(0, opts.limit) : sorted;
139
+ }
140
+
141
+ // ─── getNodeInfo() ───────────────────────────────────────────────────────────
142
+
143
+ /**
144
+ * Get detailed info for a specific node including pricing.
145
+ *
146
+ * @param {string} nodeAddress - sentnode1... address
147
+ * @returns {Promise<{ address: string, prices: object, online: boolean, country: string|null }>}
148
+ */
149
+ export async function getNodeInfo(nodeAddress) {
150
+ const prices = await getNodePrices(nodeAddress);
151
+ return {
152
+ address: nodeAddress,
153
+ prices,
154
+ online: true, // If getNodePrices didn't throw, node is reachable
155
+ };
156
+ }
157
+
158
+ // ─── getNetworkStats() ───────────────────────────────────────────────────────
159
+
160
+ /**
161
+ * Get network-wide statistics for an AI agent to understand the landscape.
162
+ *
163
+ * @returns {Promise<{
164
+ * totalNodes: number,
165
+ * byCountry: Record<string, number>,
166
+ * byProtocol: { wireguard: number, v2ray: number },
167
+ * transportReliability: Record<string, number>,
168
+ * }>}
169
+ */
170
+ export async function getNetworkStats() {
171
+ const overview = await getNetworkOverview();
172
+ return {
173
+ totalNodes: overview.totalNodes || overview.total || 0,
174
+ byCountry: overview.byCountry || {},
175
+ byProtocol: overview.byType || overview.byProtocol || { wireguard: 0, v2ray: 0 },
176
+ transportReliability: { ...TRANSPORT_SUCCESS_RATES },
177
+ };
178
+ }
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Sentinel AI Path — Environment Detection & Setup
3
+ *
4
+ * Detects OS, checks all dependencies, reports what's available.
5
+ * An AI agent calls setup() first to understand what it can do.
6
+ */
7
+
8
+ import {
9
+ verifyDependencies,
10
+ IS_ADMIN,
11
+ WG_AVAILABLE,
12
+ V2RAY_VERSION,
13
+ preflight,
14
+ } from '../index.js';
15
+ import { existsSync } from 'fs';
16
+ import { execSync } from 'child_process';
17
+ import { resolve, dirname } from 'path';
18
+ import { fileURLToPath } from 'url';
19
+
20
+ const __dirname = dirname(fileURLToPath(import.meta.url));
21
+
22
+ // ─── V2Ray Detection (comprehensive) ────────────────────────────────────────
23
+
24
+ /**
25
+ * Find V2Ray binary by checking every known location.
26
+ * This is the authoritative detection — covers env var, SDK paths, system paths.
27
+ */
28
+ function findV2Ray() {
29
+ const binary = process.platform === 'win32' ? 'v2ray.exe' : 'v2ray';
30
+
31
+ // 1. V2RAY_PATH env var (highest priority)
32
+ if (process.env.V2RAY_PATH && existsSync(process.env.V2RAY_PATH)) {
33
+ return process.env.V2RAY_PATH;
34
+ }
35
+
36
+ // 2. Use Node.js module resolution to find the SDK, then derive bin/ path
37
+ try {
38
+ const sdkMain = import.meta.resolve('sentinel-dvpn-sdk');
39
+ const sdkDir = dirname(fileURLToPath(sdkMain));
40
+ const sdkBin = resolve(sdkDir, 'bin', binary);
41
+ if (existsSync(sdkBin)) return sdkBin;
42
+ } catch {}
43
+
44
+ // 3. Walk up from __dirname looking for sentinel-dvpn-sdk/bin/
45
+ let dir = __dirname;
46
+ for (let i = 0; i < 5; i++) {
47
+ const candidate = resolve(dir, 'node_modules', 'sentinel-dvpn-sdk', 'bin', binary);
48
+ if (existsSync(candidate)) return candidate;
49
+ const sibling = resolve(dir, '..', 'sentinel-dvpn-sdk', 'bin', binary);
50
+ if (existsSync(sibling)) return sibling;
51
+ dir = resolve(dir, '..');
52
+ }
53
+
54
+ // 4. Local bin/
55
+ const localBin = resolve(__dirname, 'bin', binary);
56
+ if (existsSync(localBin)) return localBin;
57
+
58
+ // 5. Parent bin/ (monorepo layout)
59
+ const parentBin = resolve(__dirname, '..', 'bin', binary);
60
+ if (existsSync(parentBin)) return parentBin;
61
+
62
+ // 5. System paths
63
+ const systemPaths = process.platform === 'win32'
64
+ ? ['C:\\Program Files\\V2Ray\\v2ray.exe', 'C:\\Program Files (x86)\\V2Ray\\v2ray.exe']
65
+ : ['/usr/local/bin/v2ray', '/usr/bin/v2ray', '/opt/homebrew/bin/v2ray'];
66
+ for (const p of systemPaths) {
67
+ if (existsSync(p)) return p;
68
+ }
69
+
70
+ // 6. System PATH
71
+ try {
72
+ const cmd = process.platform === 'win32' ? 'where v2ray.exe' : 'which v2ray';
73
+ const result = execSync(cmd, { encoding: 'utf8', stdio: 'pipe' }).trim();
74
+ if (result) return result.split('\n')[0];
75
+ } catch { /* not in PATH */ }
76
+
77
+ return null;
78
+ }
79
+
80
+ // ─── WireGuard Detection (comprehensive) ─────────────────────────────────────
81
+
82
+ function findWireGuard() {
83
+ if (process.platform === 'win32') {
84
+ const paths = [
85
+ 'C:\\Program Files\\WireGuard\\wireguard.exe',
86
+ 'C:\\Program Files (x86)\\WireGuard\\wireguard.exe',
87
+ ];
88
+ for (const p of paths) {
89
+ if (existsSync(p)) return p;
90
+ }
91
+ } else {
92
+ const paths = ['/usr/bin/wg', '/usr/local/bin/wg', '/opt/homebrew/bin/wg'];
93
+ for (const p of paths) {
94
+ if (existsSync(p)) return p;
95
+ }
96
+ }
97
+ try {
98
+ const cmd = process.platform === 'win32' ? 'where wireguard.exe' : 'which wg';
99
+ const result = execSync(cmd, { encoding: 'utf8', stdio: 'pipe' }).trim();
100
+ if (result) return result.split('\n')[0];
101
+ } catch { /* not in PATH */ }
102
+ return null;
103
+ }
104
+
105
+ // ─── getEnvironment() ────────────────────────────────────────────────────────
106
+
107
+ /**
108
+ * Detect the current environment without changing anything.
109
+ * Uses comprehensive detection — checks env vars, SDK paths, system paths, PATH.
110
+ *
111
+ * @returns {{
112
+ * os: string,
113
+ * arch: string,
114
+ * platform: string,
115
+ * nodeVersion: string,
116
+ * admin: boolean,
117
+ * v2ray: { available: boolean, version: string|null, path: string|null },
118
+ * wireguard: { available: boolean, path: string|null, requiresAdmin: true },
119
+ * capabilities: string[],
120
+ * recommended: string[],
121
+ * }}
122
+ */
123
+ export function getEnvironment() {
124
+ const os = process.platform === 'win32' ? 'windows'
125
+ : process.platform === 'darwin' ? 'macos'
126
+ : process.platform === 'linux' ? 'linux'
127
+ : process.platform;
128
+
129
+ // V2Ray: our own comprehensive detection (not just SDK's verifyDependencies)
130
+ const v2rayPath = findV2Ray();
131
+ let v2rayVersion = null;
132
+ if (v2rayPath) {
133
+ try {
134
+ const out = execSync(`"${v2rayPath}" version`, { encoding: 'utf8', stdio: 'pipe', timeout: 5000 });
135
+ const match = out.match(/V2Ray\s+(\d+\.\d+\.\d+)/);
136
+ v2rayVersion = match ? match[1] : null;
137
+ } catch { /* version check optional */ }
138
+ }
139
+
140
+ const v2ray = {
141
+ available: !!v2rayPath,
142
+ version: v2rayVersion,
143
+ path: v2rayPath,
144
+ };
145
+
146
+ // WireGuard: our own comprehensive detection
147
+ const wgPath = findWireGuard();
148
+ const wireguard = {
149
+ available: !!wgPath,
150
+ path: wgPath,
151
+ requiresAdmin: true,
152
+ };
153
+
154
+ // What this environment can do
155
+ const capabilities = [];
156
+ if (v2ray.available) capabilities.push('v2ray');
157
+ if (wireguard.available && IS_ADMIN) capabilities.push('wireguard');
158
+ if (wireguard.available && !IS_ADMIN) capabilities.push('wireguard-needs-admin');
159
+
160
+ // What we recommend installing
161
+ const recommended = [];
162
+ if (!v2ray.available) recommended.push('v2ray — run: node setup.js');
163
+ if (!wireguard.available && os === 'windows') {
164
+ recommended.push('wireguard — run setup.js as admin for auto-install');
165
+ }
166
+ if (!wireguard.available && os === 'macos') {
167
+ recommended.push('wireguard — run: brew install wireguard-tools');
168
+ }
169
+ if (!wireguard.available && os === 'linux') {
170
+ recommended.push('wireguard — run: sudo apt install wireguard-tools');
171
+ }
172
+ if (wireguard.available && !IS_ADMIN) {
173
+ recommended.push('run as admin to use WireGuard nodes (faster, more reliable)');
174
+ }
175
+
176
+ return {
177
+ os,
178
+ arch: process.arch,
179
+ platform: `${os}-${process.arch}`,
180
+ nodeVersion: process.versions.node,
181
+ admin: IS_ADMIN,
182
+ v2ray,
183
+ wireguard,
184
+ capabilities,
185
+ recommended,
186
+ };
187
+ }
188
+
189
+ // ─── setup() ─────────────────────────────────────────────────────────────────
190
+
191
+ /**
192
+ * Full environment setup: check deps, install missing ones, report status.
193
+ * Runs preflight checks that verify everything needed for a VPN connection.
194
+ *
195
+ * Returns a FLAT structure — agents access .os, .v2ray, .wireguard directly.
196
+ * No nested .environment wrapper to misread.
197
+ *
198
+ * @returns {Promise<{
199
+ * ready: boolean,
200
+ * os: string,
201
+ * arch: string,
202
+ * platform: string,
203
+ * nodeVersion: string,
204
+ * admin: boolean,
205
+ * v2ray: boolean,
206
+ * v2rayVersion: string|null,
207
+ * v2rayPath: string|null,
208
+ * wireguard: boolean,
209
+ * wireguardPath: string|null,
210
+ * capabilities: string[],
211
+ * recommended: string[],
212
+ * preflight: object|null,
213
+ * issues: string[],
214
+ * }>}
215
+ */
216
+ export async function setup() {
217
+ const env = getEnvironment();
218
+ const issues = [];
219
+
220
+ // Run preflight checks — pass already-detected V2Ray path to avoid contradiction (BUG-1 fix)
221
+ let preflightResult = null;
222
+ try {
223
+ const preflightOpts = {};
224
+ if (env.v2ray?.path) preflightOpts.v2rayExePath = env.v2ray.path;
225
+ preflightResult = await preflight(preflightOpts);
226
+ } catch (err) {
227
+ issues.push(`Preflight failed: ${err.message}`);
228
+ }
229
+
230
+ // Check critical requirements
231
+ const nodeMajor = parseInt(process.versions.node.split('.')[0], 10);
232
+ if (nodeMajor < 20) {
233
+ issues.push(`Node.js ${process.versions.node} too old — need >= 20`);
234
+ }
235
+
236
+ if (!env.v2ray.available && !env.wireguard.available) {
237
+ issues.push('No tunnel protocol available — install V2Ray or WireGuard');
238
+ }
239
+
240
+ if (env.v2ray.available && env.v2ray.version && env.v2ray.version !== V2RAY_VERSION) {
241
+ issues.push(`V2Ray version ${env.v2ray.version} — need exactly ${V2RAY_VERSION} (5.44.1+ has bugs)`);
242
+ }
243
+
244
+ const ready = issues.length === 0 && env.capabilities.length > 0;
245
+
246
+ // Flat return — agent accesses .os, .v2ray, .admin directly
247
+ return {
248
+ ready,
249
+ os: env.os,
250
+ arch: env.arch,
251
+ platform: env.platform,
252
+ nodeVersion: env.nodeVersion,
253
+ admin: env.admin,
254
+ v2ray: env.v2ray.available,
255
+ v2rayVersion: env.v2ray.version,
256
+ v2rayPath: env.v2ray.path,
257
+ wireguard: env.wireguard.available,
258
+ wireguardPath: env.wireguard.path,
259
+ capabilities: env.capabilities,
260
+ recommended: env.recommended,
261
+ preflight: preflightResult,
262
+ issues,
263
+ // Backward compat — keep nested .environment for existing consumers
264
+ environment: env,
265
+ };
266
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Sentinel AI Path — Error Types
3
+ *
4
+ * Typed errors with machine-readable codes for AI agent error handling.
5
+ * Every error includes a `nextAction` field telling the agent what to do.
6
+ */
7
+
8
+ // ─── Error Codes ────────────────────────────────────────────────────────────
9
+
10
+ export const AiPathErrorCodes = {
11
+ // Wallet
12
+ MISSING_MNEMONIC: 'MISSING_MNEMONIC',
13
+ INVALID_MNEMONIC: 'INVALID_MNEMONIC',
14
+ INSUFFICIENT_BALANCE: 'INSUFFICIENT_BALANCE',
15
+
16
+ // Environment
17
+ SETUP_FAILED: 'SETUP_FAILED',
18
+ ENVIRONMENT_NOT_READY: 'ENVIRONMENT_NOT_READY',
19
+ V2RAY_NOT_FOUND: 'V2RAY_NOT_FOUND',
20
+ WIREGUARD_NOT_FOUND: 'WIREGUARD_NOT_FOUND',
21
+ ADMIN_REQUIRED: 'ADMIN_REQUIRED',
22
+
23
+ // Connection
24
+ CONNECT_FAILED: 'CONNECT_FAILED',
25
+ DISCONNECT_FAILED: 'DISCONNECT_FAILED',
26
+ ALREADY_CONNECTED: 'ALREADY_CONNECTED',
27
+ ALL_NODES_FAILED: 'ALL_NODES_FAILED',
28
+ NO_NODES_IN_COUNTRY: 'NO_NODES_IN_COUNTRY',
29
+ NODE_OFFLINE: 'NODE_OFFLINE',
30
+ HANDSHAKE_FAILED: 'HANDSHAKE_FAILED',
31
+ TUNNEL_FAILED: 'TUNNEL_FAILED',
32
+ TIMEOUT: 'TIMEOUT',
33
+
34
+ // Validation
35
+ INVALID_OPTIONS: 'INVALID_OPTIONS',
36
+
37
+ // Discovery
38
+ DISCOVERY_FAILED: 'DISCOVERY_FAILED',
39
+ WALLET_FAILED: 'WALLET_FAILED',
40
+ BALANCE_FAILED: 'BALANCE_FAILED',
41
+ VERIFY_FAILED: 'VERIFY_FAILED',
42
+ };
43
+
44
+ // ─── Next Actions (machine-readable) ────────────────────────────────────────
45
+
46
+ export const NextActions = {
47
+ CREATE_WALLET: 'create_wallet',
48
+ FUND_WALLET: 'fund_wallet',
49
+ RUN_SETUP: 'run_setup',
50
+ RUN_AS_ADMIN: 'run_as_admin',
51
+ TRY_DIFFERENT_NODE: 'try_different_node',
52
+ TRY_DIFFERENT_COUNTRY: 'try_different_country',
53
+ TRY_V2RAY: 'try_v2ray',
54
+ TRY_WIREGUARD: 'try_wireguard',
55
+ DISCONNECT_FIRST: 'disconnect',
56
+ RETRY: 'retry',
57
+ NONE: 'none',
58
+ };
59
+
60
+ // ─── Error Class ────────────────────────────────────────────────────────────
61
+
62
+ export class AiPathError extends Error {
63
+ /**
64
+ * @param {string} code - Machine-readable error code from AiPathErrorCodes
65
+ * @param {string} message - Human-readable message
66
+ * @param {object} [details] - Extra context for the agent
67
+ * @param {string} [nextAction] - What the agent should do next (from NextActions)
68
+ */
69
+ constructor(code, message, details = null, nextAction = NextActions.RETRY) {
70
+ super(message);
71
+ this.name = 'AiPathError';
72
+ this.code = code;
73
+ this.details = details;
74
+ this.nextAction = nextAction;
75
+ }
76
+
77
+ toJSON() {
78
+ return {
79
+ name: this.name,
80
+ code: this.code,
81
+ message: this.message,
82
+ details: this.details,
83
+ nextAction: this.nextAction,
84
+ };
85
+ }
86
+ }