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.
- package/CHANGELOG.md +446 -0
- package/LICENSE +21 -0
- package/README.md +75 -0
- package/ai-path/ADMIN-ELEVATION.md +116 -0
- package/ai-path/AI-MANIFESTO.md +185 -0
- package/ai-path/BREAKING.md +74 -0
- package/ai-path/CHECKLIST.md +619 -0
- package/ai-path/CONNECTION-STEPS.md +724 -0
- package/ai-path/DECISION-TREE.md +378 -0
- package/ai-path/DEPENDENCIES.md +459 -0
- package/ai-path/E2E-FLOW.md +1555 -0
- package/ai-path/FAILURES.md +403 -0
- package/ai-path/GUIDE.md +1217 -0
- package/ai-path/README.md +558 -0
- package/ai-path/SPLIT-TUNNEL.md +266 -0
- package/ai-path/cli.js +535 -0
- package/ai-path/connect.js +884 -0
- package/ai-path/discover.js +178 -0
- package/ai-path/environment.js +266 -0
- package/ai-path/errors.js +86 -0
- package/ai-path/examples/autonomous-agent.mjs +220 -0
- package/ai-path/examples/multi-region.mjs +174 -0
- package/ai-path/examples/one-shot.mjs +31 -0
- package/ai-path/index.js +60 -0
- package/ai-path/pricing.js +136 -0
- package/ai-path/recommend.js +413 -0
- package/ai-path/run-admin.vbs +25 -0
- package/ai-path/setup.js +291 -0
- package/ai-path/wallet.js +137 -0
- package/app-helpers.js +363 -0
- package/app-settings.js +95 -0
- package/app-types.js +267 -0
- package/audit.js +847 -0
- package/batch.js +293 -0
- package/bin/setup.js +376 -0
- package/chain/authz.js +109 -0
- package/chain/broadcast.js +472 -0
- package/chain/client.js +160 -0
- package/chain/fee-grants.js +305 -0
- package/chain/index.js +891 -0
- package/chain/lcd.js +313 -0
- package/chain/queries.js +547 -0
- package/chain/rpc.js +408 -0
- package/chain/wallet.js +141 -0
- package/cli/config.js +143 -0
- package/cli/index.js +463 -0
- package/cli/output.js +182 -0
- package/cli.js +491 -0
- package/client/index.js +251 -0
- package/client.js +271 -0
- package/config/index.js +255 -0
- package/connection/connect.js +849 -0
- package/connection/disconnect.js +180 -0
- package/connection/discovery.js +321 -0
- package/connection/index.js +76 -0
- package/connection/proxy.js +148 -0
- package/connection/resilience.js +428 -0
- package/connection/security.js +232 -0
- package/connection/state.js +369 -0
- package/connection/tunnel.js +691 -0
- package/consumer.js +132 -0
- package/cosmjs-setup.js +1884 -0
- package/defaults.js +366 -0
- package/disk-cache.js +107 -0
- package/dist/client.d.ts +108 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +400 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/errors/index.js +112 -0
- package/errors.js +218 -0
- package/examples/README.md +64 -0
- package/examples/connect-direct.mjs +106 -0
- package/examples/connect-plan.mjs +125 -0
- package/examples/error-handling.mjs +109 -0
- package/examples/query-nodes.mjs +94 -0
- package/examples/wallet-basics.mjs +61 -0
- package/generated/amino/amino.ts +9 -0
- package/generated/cosmos/base/v1beta1/coin.ts +365 -0
- package/generated/cosmos_proto/cosmos.ts +323 -0
- package/generated/gogoproto/gogo.ts +9 -0
- package/generated/google/protobuf/descriptor.ts +7601 -0
- package/generated/google/protobuf/duration.ts +208 -0
- package/generated/google/protobuf/timestamp.ts +238 -0
- package/generated/sentinel/lease/v1/events.ts +924 -0
- package/generated/sentinel/lease/v1/lease.ts +292 -0
- package/generated/sentinel/lease/v1/msg.ts +949 -0
- package/generated/sentinel/lease/v1/params.ts +164 -0
- package/generated/sentinel/node/v3/events.ts +881 -0
- package/generated/sentinel/node/v3/msg.ts +1002 -0
- package/generated/sentinel/node/v3/node.ts +263 -0
- package/generated/sentinel/node/v3/params.ts +183 -0
- package/generated/sentinel/plan/v3/events.ts +675 -0
- package/generated/sentinel/plan/v3/msg.ts +1191 -0
- package/generated/sentinel/plan/v3/plan.ts +283 -0
- package/generated/sentinel/provider/v2/events.ts +171 -0
- package/generated/sentinel/provider/v2/msg.ts +480 -0
- package/generated/sentinel/provider/v2/params.ts +131 -0
- package/generated/sentinel/provider/v2/provider.ts +246 -0
- package/generated/sentinel/session/v3/events.ts +480 -0
- package/generated/sentinel/session/v3/msg.ts +616 -0
- package/generated/sentinel/session/v3/params.ts +260 -0
- package/generated/sentinel/session/v3/proof.ts +180 -0
- package/generated/sentinel/session/v3/session.ts +384 -0
- package/generated/sentinel/subscription/v3/events.ts +1181 -0
- package/generated/sentinel/subscription/v3/msg.ts +1305 -0
- package/generated/sentinel/subscription/v3/params.ts +167 -0
- package/generated/sentinel/subscription/v3/subscription.ts +315 -0
- package/generated/sentinel/types/v1/bandwidth.ts +124 -0
- package/generated/sentinel/types/v1/price.ts +149 -0
- package/generated/sentinel/types/v1/renewal.ts +87 -0
- package/generated/sentinel/types/v1/status.ts +54 -0
- package/generated/typeRegistry.ts +27 -0
- package/index.js +486 -0
- package/node-connect.js +3015 -0
- package/operator.js +134 -0
- package/package.json +113 -0
- package/plan-operations.js +199 -0
- package/preflight.js +352 -0
- package/pricing/index.js +262 -0
- package/proto/amino/amino.proto +84 -0
- package/proto/cosmos/base/v1beta1/coin.proto +61 -0
- package/proto/cosmos_proto/cosmos.proto +112 -0
- package/proto/gogoproto/gogo.proto +145 -0
- package/proto/google/api/annotations.proto +31 -0
- package/proto/google/api/http.proto +370 -0
- package/proto/google/protobuf/any.proto +106 -0
- package/proto/google/protobuf/duration.proto +115 -0
- package/proto/google/protobuf/timestamp.proto +145 -0
- package/proto/sentinel/lease/v1/events.proto +52 -0
- package/proto/sentinel/lease/v1/genesis.proto +15 -0
- package/proto/sentinel/lease/v1/lease.proto +25 -0
- package/proto/sentinel/lease/v1/msg.proto +62 -0
- package/proto/sentinel/lease/v1/params.proto +17 -0
- package/proto/sentinel/node/v3/events.proto +50 -0
- package/proto/sentinel/node/v3/genesis.proto +15 -0
- package/proto/sentinel/node/v3/msg.proto +63 -0
- package/proto/sentinel/node/v3/node.proto +27 -0
- package/proto/sentinel/node/v3/params.proto +21 -0
- package/proto/sentinel/node/v3/querier.proto +63 -0
- package/proto/sentinel/plan/v3/events.proto +41 -0
- package/proto/sentinel/plan/v3/genesis.proto +21 -0
- package/proto/sentinel/plan/v3/msg.proto +83 -0
- package/proto/sentinel/plan/v3/plan.proto +32 -0
- package/proto/sentinel/plan/v3/querier.proto +53 -0
- package/proto/sentinel/provider/v2/events.proto +16 -0
- package/proto/sentinel/provider/v2/genesis.proto +15 -0
- package/proto/sentinel/provider/v2/msg.proto +35 -0
- package/proto/sentinel/provider/v2/params.proto +17 -0
- package/proto/sentinel/provider/v2/provider.proto +24 -0
- package/proto/sentinel/provider/v3/genesis.proto +15 -0
- package/proto/sentinel/provider/v3/params.proto +13 -0
- package/proto/sentinel/session/v3/events.proto +30 -0
- package/proto/sentinel/session/v3/genesis.proto +15 -0
- package/proto/sentinel/session/v3/msg.proto +50 -0
- package/proto/sentinel/session/v3/params.proto +25 -0
- package/proto/sentinel/session/v3/proof.proto +25 -0
- package/proto/sentinel/session/v3/querier.proto +100 -0
- package/proto/sentinel/session/v3/session.proto +50 -0
- package/proto/sentinel/subscription/v2/allocation.proto +21 -0
- package/proto/sentinel/subscription/v2/payout.proto +22 -0
- package/proto/sentinel/subscription/v3/events.proto +65 -0
- package/proto/sentinel/subscription/v3/genesis.proto +17 -0
- package/proto/sentinel/subscription/v3/msg.proto +83 -0
- package/proto/sentinel/subscription/v3/params.proto +21 -0
- package/proto/sentinel/subscription/v3/subscription.proto +33 -0
- package/proto/sentinel/types/v1/bandwidth.proto +19 -0
- package/proto/sentinel/types/v1/price.proto +21 -0
- package/proto/sentinel/types/v1/renewal.proto +21 -0
- package/proto/sentinel/types/v1/status.proto +16 -0
- package/protocol/encoding.js +341 -0
- package/protocol/events.js +361 -0
- package/protocol/handshake.js +297 -0
- package/protocol/index.js +15 -0
- package/protocol/messages.js +346 -0
- package/protocol/plans.js +199 -0
- package/protocol/v2ray.js +268 -0
- package/protocol/v3.js +723 -0
- package/protocol/wireguard.js +125 -0
- package/security/index.js +132 -0
- package/session-manager.js +329 -0
- package/session-tracker.js +80 -0
- package/setup.js +376 -0
- package/speedtest/index.js +528 -0
- package/speedtest.js +567 -0
- package/src/client.ts +502 -0
- package/src/index.ts +20 -0
- package/state/index.js +347 -0
- package/state.js +516 -0
- package/test-all-chain-ops.js +493 -0
- package/test-all-logic.js +199 -0
- package/test-all-msg-types.js +292 -0
- package/test-every-connection.js +208 -0
- package/test-feegrant-connect.js +98 -0
- package/test-logic.js +148 -0
- package/test-mainnet.js +176 -0
- package/test-plan-lifecycle.js +335 -0
- package/tls-trust.js +132 -0
- package/tsconfig.build.json +20 -0
- package/tsconfig.json +34 -0
- package/types/chain.d.ts +746 -0
- package/types/connection.d.ts +425 -0
- package/types/errors.d.ts +174 -0
- package/types/index.d.ts +1380 -0
- package/types/nodes.d.ts +187 -0
- package/types/pricing.d.ts +156 -0
- package/types/protocol.d.ts +332 -0
- package/types/session.d.ts +236 -0
- package/types/settings.d.ts +192 -0
- package/v3protocol.js +1053 -0
- package/wallet/index.js +153 -0
- package/wireguard.js +307 -0
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sentinel AI Path — Decision Engine for Autonomous Agents
|
|
3
|
+
*
|
|
4
|
+
* An autonomous agent calls recommend() BEFORE connect().
|
|
5
|
+
* It receives structured recommendations with alternatives,
|
|
6
|
+
* cost estimates, warnings, and fallback strategies.
|
|
7
|
+
*
|
|
8
|
+
* The agent makes the final decision — the SDK never decides for it.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
queryOnlineNodes,
|
|
13
|
+
fetchActiveNodes,
|
|
14
|
+
filterNodes,
|
|
15
|
+
getNodePrices,
|
|
16
|
+
formatP2P,
|
|
17
|
+
IS_ADMIN,
|
|
18
|
+
WG_AVAILABLE,
|
|
19
|
+
TRANSPORT_SUCCESS_RATES,
|
|
20
|
+
COUNTRY_MAP,
|
|
21
|
+
// v1.5.0: RPC queries (protobuf, ~10x faster than LCD for node lists)
|
|
22
|
+
createRpcQueryClientWithFallback,
|
|
23
|
+
rpcQueryNodes,
|
|
24
|
+
} from '../index.js';
|
|
25
|
+
|
|
26
|
+
// ─── Country Proximity Map ──────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
const REGION_MAP = {
|
|
29
|
+
// Western Europe
|
|
30
|
+
'DE': ['AT', 'CH', 'NL', 'BE', 'LU', 'FR', 'CZ', 'PL', 'DK'],
|
|
31
|
+
'FR': ['BE', 'LU', 'CH', 'DE', 'ES', 'IT', 'NL', 'GB'],
|
|
32
|
+
'GB': ['IE', 'NL', 'FR', 'BE', 'DE', 'DK', 'NO'],
|
|
33
|
+
'NL': ['BE', 'DE', 'GB', 'LU', 'FR', 'DK'],
|
|
34
|
+
// Nordic
|
|
35
|
+
'SE': ['NO', 'DK', 'FI', 'DE', 'NL', 'EE'],
|
|
36
|
+
'NO': ['SE', 'DK', 'FI', 'GB', 'DE', 'NL'],
|
|
37
|
+
'FI': ['SE', 'EE', 'NO', 'DK', 'LV', 'LT'],
|
|
38
|
+
'DK': ['SE', 'NO', 'DE', 'NL', 'GB'],
|
|
39
|
+
// Eastern Europe
|
|
40
|
+
'PL': ['CZ', 'SK', 'DE', 'LT', 'UA', 'AT'],
|
|
41
|
+
'CZ': ['SK', 'DE', 'AT', 'PL'],
|
|
42
|
+
'RO': ['BG', 'HU', 'MD', 'UA', 'RS'],
|
|
43
|
+
'UA': ['PL', 'RO', 'MD', 'HU', 'SK', 'CZ'],
|
|
44
|
+
// Southern Europe
|
|
45
|
+
'IT': ['CH', 'AT', 'FR', 'SI', 'HR'],
|
|
46
|
+
'ES': ['PT', 'FR', 'IT'],
|
|
47
|
+
'GR': ['BG', 'TR', 'CY', 'AL', 'MK', 'IT'],
|
|
48
|
+
'TR': ['GR', 'BG', 'GE', 'CY'],
|
|
49
|
+
// North America
|
|
50
|
+
'US': ['CA', 'MX'],
|
|
51
|
+
'CA': ['US'],
|
|
52
|
+
// Asia
|
|
53
|
+
'JP': ['KR', 'TW', 'HK', 'SG'],
|
|
54
|
+
'KR': ['JP', 'TW', 'HK', 'SG'],
|
|
55
|
+
'SG': ['MY', 'ID', 'TH', 'VN', 'HK', 'JP', 'KR', 'TW'],
|
|
56
|
+
'IN': ['SG', 'AE', 'LK', 'BD'],
|
|
57
|
+
'AE': ['IN', 'SG', 'BH', 'QA', 'SA'],
|
|
58
|
+
// Oceania
|
|
59
|
+
'AU': ['NZ', 'SG', 'JP'],
|
|
60
|
+
'NZ': ['AU', 'SG'],
|
|
61
|
+
// South America
|
|
62
|
+
'BR': ['AR', 'CL', 'UY', 'CO'],
|
|
63
|
+
'AR': ['BR', 'CL', 'UY'],
|
|
64
|
+
// Africa
|
|
65
|
+
'ZA': ['NA', 'BW', 'MZ', 'KE'],
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get nearby countries sorted by proximity.
|
|
70
|
+
*/
|
|
71
|
+
function getNearbyCountries(countryCode) {
|
|
72
|
+
const code = countryCode.toUpperCase();
|
|
73
|
+
const nearby = REGION_MAP[code] || [];
|
|
74
|
+
return nearby;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Normalize a country input to ISO code.
|
|
79
|
+
*/
|
|
80
|
+
function toCountryCode(input) {
|
|
81
|
+
if (!input) return null;
|
|
82
|
+
const upper = input.toUpperCase().trim();
|
|
83
|
+
if (upper.length === 2) return upper;
|
|
84
|
+
// Check COUNTRY_MAP from SDK if available
|
|
85
|
+
if (COUNTRY_MAP) {
|
|
86
|
+
for (const [name, code] of Object.entries(COUNTRY_MAP)) {
|
|
87
|
+
if (name.toUpperCase() === upper) return code;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Common names
|
|
91
|
+
const common = {
|
|
92
|
+
'GERMANY': 'DE', 'UNITED STATES': 'US', 'USA': 'US', 'UNITED KINGDOM': 'GB',
|
|
93
|
+
'UK': 'GB', 'FRANCE': 'FR', 'JAPAN': 'JP', 'CANADA': 'CA', 'AUSTRALIA': 'AU',
|
|
94
|
+
'NETHERLANDS': 'NL', 'SWITZERLAND': 'CH', 'SWEDEN': 'SE', 'NORWAY': 'NO',
|
|
95
|
+
'SINGAPORE': 'SG', 'SOUTH KOREA': 'KR', 'KOREA': 'KR', 'INDIA': 'IN',
|
|
96
|
+
'BRAZIL': 'BR', 'SPAIN': 'ES', 'ITALY': 'IT', 'TURKEY': 'TR', 'RUSSIA': 'RU',
|
|
97
|
+
'UKRAINE': 'UA', 'POLAND': 'PL', 'ROMANIA': 'RO', 'FINLAND': 'FI',
|
|
98
|
+
'DENMARK': 'DK', 'IRELAND': 'IE', 'PORTUGAL': 'PT', 'AUSTRIA': 'AT',
|
|
99
|
+
'CZECH REPUBLIC': 'CZ', 'CZECHIA': 'CZ', 'HUNGARY': 'HU', 'BELGIUM': 'BE',
|
|
100
|
+
'SOUTH AFRICA': 'ZA', 'ARGENTINA': 'AR', 'MEXICO': 'MX', 'COLOMBIA': 'CO',
|
|
101
|
+
'HONG KONG': 'HK', 'TAIWAN': 'TW', 'THAILAND': 'TH', 'VIETNAM': 'VN',
|
|
102
|
+
'INDONESIA': 'ID', 'MALAYSIA': 'MY', 'PHILIPPINES': 'PH', 'NEW ZEALAND': 'NZ',
|
|
103
|
+
'UNITED ARAB EMIRATES': 'AE', 'UAE': 'AE', 'ISRAEL': 'IL',
|
|
104
|
+
};
|
|
105
|
+
return common[upper] || null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ─── recommend() ─────────────────────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Generate structured recommendations for an autonomous AI agent.
|
|
112
|
+
*
|
|
113
|
+
* The agent provides its preferences. The SDK returns ranked options
|
|
114
|
+
* with cost estimates, warnings, and fallback strategies.
|
|
115
|
+
* The agent makes the final decision.
|
|
116
|
+
*
|
|
117
|
+
* @param {object} preferences
|
|
118
|
+
* @param {string} [preferences.country] - Preferred country (name or ISO code)
|
|
119
|
+
* @param {number} [preferences.budget] - Available budget in udvpn
|
|
120
|
+
* @param {'reliability'|'cost'|'speed'|'location'} [preferences.priority='reliability'] - What matters most
|
|
121
|
+
* @param {number} [preferences.gigabytes=1] - Planned data usage
|
|
122
|
+
* @param {string} [preferences.protocol] - Force 'wireguard' or 'v2ray'
|
|
123
|
+
* @param {boolean} [preferences.strictCountry=false] - If true, fail if exact country unavailable
|
|
124
|
+
* @param {number} [preferences.maxNodes=50] - Max nodes to evaluate
|
|
125
|
+
*
|
|
126
|
+
* @returns {Promise<{
|
|
127
|
+
* action: 'connect'|'connect-fallback'|'cannot-connect',
|
|
128
|
+
* confidence: number,
|
|
129
|
+
* primary: object|null,
|
|
130
|
+
* alternatives: object[],
|
|
131
|
+
* fallbackStrategy: string,
|
|
132
|
+
* estimatedCost: { udvpn: number, p2p: string },
|
|
133
|
+
* warnings: string[],
|
|
134
|
+
* reasoning: string[],
|
|
135
|
+
* capabilities: { wireguard: boolean, v2ray: boolean, admin: boolean },
|
|
136
|
+
* }>}
|
|
137
|
+
*/
|
|
138
|
+
export async function recommend(preferences = {}) {
|
|
139
|
+
if (preferences && typeof preferences !== 'object') {
|
|
140
|
+
throw new Error('recommend(): preferences must be an object or undefined');
|
|
141
|
+
}
|
|
142
|
+
const {
|
|
143
|
+
country = null,
|
|
144
|
+
budget = 0,
|
|
145
|
+
priority = 'reliability',
|
|
146
|
+
gigabytes = 1,
|
|
147
|
+
protocol = null,
|
|
148
|
+
strictCountry = false,
|
|
149
|
+
maxNodes = 50,
|
|
150
|
+
} = preferences;
|
|
151
|
+
|
|
152
|
+
const warnings = [];
|
|
153
|
+
const reasoning = [];
|
|
154
|
+
const countryCode = toCountryCode(country);
|
|
155
|
+
|
|
156
|
+
// ─── Capabilities assessment ───────────────────────────────────────────
|
|
157
|
+
|
|
158
|
+
const canWG = WG_AVAILABLE && IS_ADMIN;
|
|
159
|
+
// Check actual V2Ray availability instead of assuming true
|
|
160
|
+
let canV2 = false;
|
|
161
|
+
try {
|
|
162
|
+
const { getEnvironment } = await import('./environment.js');
|
|
163
|
+
canV2 = getEnvironment().v2ray?.available ?? false;
|
|
164
|
+
} catch { canV2 = true; /* fallback: assume available if detection fails */ }
|
|
165
|
+
const capabilities = { wireguard: canWG, v2ray: canV2, admin: IS_ADMIN };
|
|
166
|
+
|
|
167
|
+
if (protocol === 'wireguard' && !canWG) {
|
|
168
|
+
warnings.push('WireGuard requested but not available (need admin + WireGuard installed). Falling back to V2Ray.');
|
|
169
|
+
reasoning.push('Protocol constraint: WireGuard unavailable, using V2Ray');
|
|
170
|
+
}
|
|
171
|
+
if (!IS_ADMIN && WG_AVAILABLE) {
|
|
172
|
+
warnings.push('WireGuard installed but not admin — running as admin unlocks faster WireGuard nodes');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ─── Fetch nodes ───────────────────────────────────────────────────────
|
|
176
|
+
|
|
177
|
+
reasoning.push('Fetching active nodes from chain...');
|
|
178
|
+
let allNodes;
|
|
179
|
+
try {
|
|
180
|
+
// fetchActiveNodes returns raw chain data (no country/location).
|
|
181
|
+
// If country filter needed, we need enriched data from queryOnlineNodes.
|
|
182
|
+
if (countryCode) {
|
|
183
|
+
reasoning.push('Country filter requested — probing nodes for location data...');
|
|
184
|
+
allNodes = await queryOnlineNodes({ maxNodes: maxNodes * 3 });
|
|
185
|
+
} else {
|
|
186
|
+
// v1.5.0: Try RPC first (protobuf, ~10x faster), fall back to LCD
|
|
187
|
+
try {
|
|
188
|
+
const rpcClient = await createRpcQueryClientWithFallback();
|
|
189
|
+
allNodes = await rpcQueryNodes(rpcClient, { status: 1, limit: 5000 });
|
|
190
|
+
reasoning.push('Used RPC query (fast path)');
|
|
191
|
+
} catch {
|
|
192
|
+
allNodes = await fetchActiveNodes();
|
|
193
|
+
reasoning.push('Used LCD query (fallback)');
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
reasoning.push(`Found ${allNodes.length} active nodes`);
|
|
197
|
+
} catch (err) {
|
|
198
|
+
return {
|
|
199
|
+
action: 'cannot-connect',
|
|
200
|
+
confidence: 0,
|
|
201
|
+
primary: null,
|
|
202
|
+
alternatives: [],
|
|
203
|
+
fallbackStrategy: 'none',
|
|
204
|
+
estimatedCost: { udvpn: 0, p2p: '0 P2P' },
|
|
205
|
+
warnings: [`Chain query failed: ${err.message}`],
|
|
206
|
+
reasoning: ['Cannot fetch nodes — network may be unreachable'],
|
|
207
|
+
capabilities,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ─── Filter by protocol ────────────────────────────────────────────────
|
|
212
|
+
|
|
213
|
+
let candidates = [...allNodes];
|
|
214
|
+
const effectiveProtocol = protocol === 'wireguard' && canWG ? 'wireguard'
|
|
215
|
+
: protocol === 'v2ray' ? 'v2ray'
|
|
216
|
+
: null; // auto
|
|
217
|
+
|
|
218
|
+
// Only filter by protocol if nodes have service_type data (enriched/probed nodes).
|
|
219
|
+
// Raw chain data from fetchActiveNodes() does NOT include service_type.
|
|
220
|
+
const hasServiceType = candidates.some(n => n.service_type !== undefined || n.serviceType !== undefined);
|
|
221
|
+
|
|
222
|
+
if (hasServiceType) {
|
|
223
|
+
if (effectiveProtocol === 'wireguard') {
|
|
224
|
+
candidates = candidates.filter(n => String(n.serviceType || n.service_type || '').toLowerCase() === 'wireguard');
|
|
225
|
+
reasoning.push(`Filtered to ${candidates.length} WireGuard nodes`);
|
|
226
|
+
} else if (effectiveProtocol === 'v2ray') {
|
|
227
|
+
candidates = candidates.filter(n => String(n.serviceType || n.service_type || '').toLowerCase() === 'v2ray');
|
|
228
|
+
reasoning.push(`Filtered to ${candidates.length} V2Ray nodes`);
|
|
229
|
+
} else if (!canWG) {
|
|
230
|
+
candidates = candidates.filter(n => String(n.serviceType || n.service_type || '').toLowerCase() === 'v2ray');
|
|
231
|
+
reasoning.push(`No admin — filtered to ${candidates.length} V2Ray nodes`);
|
|
232
|
+
}
|
|
233
|
+
} else {
|
|
234
|
+
reasoning.push(`${candidates.length} nodes from chain (protocol unknown until probe — connectAuto handles selection)`);
|
|
235
|
+
if (!canWG) {
|
|
236
|
+
reasoning.push('No admin — connectAuto will auto-select V2Ray nodes');
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ─── Filter by country ─────────────────────────────────────────────────
|
|
241
|
+
|
|
242
|
+
let exactCountryNodes = [];
|
|
243
|
+
let nearbyNodes = [];
|
|
244
|
+
let anyNodes = candidates;
|
|
245
|
+
|
|
246
|
+
if (countryCode) {
|
|
247
|
+
// Try exact country match
|
|
248
|
+
exactCountryNodes = filterNodes(candidates, { country: countryCode });
|
|
249
|
+
reasoning.push(`${exactCountryNodes.length} nodes in ${country} (${countryCode})`);
|
|
250
|
+
|
|
251
|
+
if (exactCountryNodes.length === 0 && !strictCountry) {
|
|
252
|
+
// Try nearby countries
|
|
253
|
+
const nearby = getNearbyCountries(countryCode);
|
|
254
|
+
reasoning.push(`No nodes in ${countryCode}. Checking nearby: ${nearby.join(', ')}`);
|
|
255
|
+
|
|
256
|
+
for (const nc of nearby) {
|
|
257
|
+
const found = filterNodes(candidates, { country: nc });
|
|
258
|
+
if (found.length > 0) {
|
|
259
|
+
nearbyNodes.push(...found.map(n => ({ ...n, _fallbackCountry: nc })));
|
|
260
|
+
reasoning.push(` Found ${found.length} nodes in ${nc}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// ─── Score and rank ────────────────────────────────────────────────────
|
|
267
|
+
|
|
268
|
+
function scoreNode(node, isExactCountry, isNearby) {
|
|
269
|
+
let score = 50; // base
|
|
270
|
+
|
|
271
|
+
// Protocol bonus
|
|
272
|
+
const isWG = String(node.serviceType || node.service_type || '').toLowerCase() === 'wireguard';
|
|
273
|
+
if (isWG) score += 15; // WireGuard more reliable
|
|
274
|
+
|
|
275
|
+
// Country bonus
|
|
276
|
+
if (isExactCountry) score += 30;
|
|
277
|
+
else if (isNearby) score += 15;
|
|
278
|
+
|
|
279
|
+
// Pricing bonus (cheaper = better if priority is cost)
|
|
280
|
+
const gbPrices = node.gigabyte_prices || [];
|
|
281
|
+
const udvpnPrice = gbPrices.find(p => p.denom === 'udvpn');
|
|
282
|
+
const price = parseInt(udvpnPrice?.quote_value || udvpnPrice?.amount || '999999999', 10);
|
|
283
|
+
if (priority === 'cost') {
|
|
284
|
+
score += Math.max(0, 20 - (price / 50000)); // cheaper gets more points
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Quality score from SDK (if enriched)
|
|
288
|
+
if (node.qualityScore) score += node.qualityScore * 0.2;
|
|
289
|
+
|
|
290
|
+
// Peer count: fewer peers = less loaded
|
|
291
|
+
if (node.peers !== undefined) {
|
|
292
|
+
if (node.peers < 5) score += 10;
|
|
293
|
+
else if (node.peers < 20) score += 5;
|
|
294
|
+
else score -= 5;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return { ...node, _score: Math.round(score), _price: price, _isWG: isWG };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Build ranked list
|
|
301
|
+
const ranked = [];
|
|
302
|
+
|
|
303
|
+
for (const n of exactCountryNodes) {
|
|
304
|
+
ranked.push(scoreNode(n, true, false));
|
|
305
|
+
}
|
|
306
|
+
for (const n of nearbyNodes) {
|
|
307
|
+
ranked.push(scoreNode(n, false, true));
|
|
308
|
+
}
|
|
309
|
+
// Fill rest from any nodes (not already included)
|
|
310
|
+
const included = new Set(ranked.map(n => n.address || n.acc_address));
|
|
311
|
+
for (const n of anyNodes) {
|
|
312
|
+
const addr = n.address || n.acc_address;
|
|
313
|
+
if (!included.has(addr)) {
|
|
314
|
+
ranked.push(scoreNode(n, false, false));
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Sort by score descending
|
|
319
|
+
ranked.sort((a, b) => b._score - a._score);
|
|
320
|
+
const top = ranked.slice(0, maxNodes);
|
|
321
|
+
|
|
322
|
+
// ─── Build recommendation ──────────────────────────────────────────────
|
|
323
|
+
|
|
324
|
+
if (top.length === 0) {
|
|
325
|
+
return {
|
|
326
|
+
action: 'cannot-connect',
|
|
327
|
+
confidence: 0,
|
|
328
|
+
primary: null,
|
|
329
|
+
alternatives: [],
|
|
330
|
+
fallbackStrategy: strictCountry ? 'fail' : 'none',
|
|
331
|
+
estimatedCost: { udvpn: 0, p2p: '0 P2P' },
|
|
332
|
+
warnings: [`No nodes available${country ? ` for ${country}` : ''}`],
|
|
333
|
+
reasoning,
|
|
334
|
+
capabilities,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (countryCode && exactCountryNodes.length === 0 && strictCountry) {
|
|
339
|
+
return {
|
|
340
|
+
action: 'cannot-connect',
|
|
341
|
+
confidence: 0,
|
|
342
|
+
primary: null,
|
|
343
|
+
alternatives: top.slice(0, 5).map(formatNode),
|
|
344
|
+
fallbackStrategy: 'fail — strictCountry is true',
|
|
345
|
+
estimatedCost: { udvpn: 0, p2p: '0 P2P' },
|
|
346
|
+
warnings: [`No nodes in ${country} (${countryCode}). strictCountry=true prevents fallback.`],
|
|
347
|
+
reasoning,
|
|
348
|
+
capabilities,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const primary = top[0];
|
|
353
|
+
const alternatives = top.slice(1, 6).map(formatNode);
|
|
354
|
+
const gasCost = 40000;
|
|
355
|
+
const sessionCost = (primary._price || 100000) * gigabytes;
|
|
356
|
+
const totalCost = sessionCost + gasCost;
|
|
357
|
+
|
|
358
|
+
// Budget check
|
|
359
|
+
if (budget > 0 && budget < totalCost) {
|
|
360
|
+
warnings.push(`Budget (${formatP2P(budget)}) may be insufficient for ${gigabytes} GB (estimated ${formatP2P(totalCost)})`);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Determine action
|
|
364
|
+
let action = 'connect';
|
|
365
|
+
let confidence = 0.9;
|
|
366
|
+
|
|
367
|
+
if (countryCode && exactCountryNodes.length === 0) {
|
|
368
|
+
action = 'connect-fallback';
|
|
369
|
+
confidence = 0.7;
|
|
370
|
+
const fc = primary._fallbackCountry || 'nearest available';
|
|
371
|
+
warnings.push(`Exact country ${country} not available. Recommending ${fc} as fallback.`);
|
|
372
|
+
reasoning.push(`Fallback: ${country} → ${fc}`);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Determine fallback strategy
|
|
376
|
+
let fallbackStrategy = 'auto — SDK tries next node on failure';
|
|
377
|
+
if (countryCode && exactCountryNodes.length > 0) {
|
|
378
|
+
fallbackStrategy = `${exactCountryNodes.length} nodes in ${country}; SDK retries within country`;
|
|
379
|
+
} else if (nearbyNodes.length > 0) {
|
|
380
|
+
fallbackStrategy = `nearest-country — ${nearbyNodes.length} nodes in nearby countries`;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return {
|
|
384
|
+
action,
|
|
385
|
+
confidence,
|
|
386
|
+
primary: formatNode(primary),
|
|
387
|
+
alternatives,
|
|
388
|
+
fallbackStrategy,
|
|
389
|
+
estimatedCost: { udvpn: totalCost, p2p: formatP2P(totalCost) },
|
|
390
|
+
warnings,
|
|
391
|
+
reasoning,
|
|
392
|
+
capabilities,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Format a node for the recommendation response.
|
|
398
|
+
*/
|
|
399
|
+
function formatNode(node) {
|
|
400
|
+
return {
|
|
401
|
+
address: node.address || node.acc_address,
|
|
402
|
+
country: node.country || node._fallbackCountry || null,
|
|
403
|
+
protocol: node._isWG ? 'wireguard' : 'v2ray',
|
|
404
|
+
score: node._score || 0,
|
|
405
|
+
pricePerGb: node._price ? { udvpn: node._price, p2p: formatP2P(node._price) } : null,
|
|
406
|
+
peers: node.peers ?? null,
|
|
407
|
+
reason: node._fallbackCountry
|
|
408
|
+
? `Fallback from requested country (nearest: ${node._fallbackCountry})`
|
|
409
|
+
: node._score >= 80 ? 'High reliability score'
|
|
410
|
+
: node._score >= 60 ? 'Good match'
|
|
411
|
+
: 'Available',
|
|
412
|
+
};
|
|
413
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
' run-admin.vbs
|
|
2
|
+
' Launches a Node.js script as Administrator.
|
|
3
|
+
' UAC prompt appears ONCE. Required for WireGuard tunnel operations.
|
|
4
|
+
'
|
|
5
|
+
' Usage: Double-click this file, OR:
|
|
6
|
+
' cscript run-admin.vbs (runs setup.js)
|
|
7
|
+
' cscript run-admin.vbs test-wireguard.mjs (runs specific script)
|
|
8
|
+
|
|
9
|
+
Dim oShell, oFSO, sDir, sScript, sCmd
|
|
10
|
+
Set oShell = CreateObject("Shell.Application")
|
|
11
|
+
Set oFSO = CreateObject("Scripting.FileSystemObject")
|
|
12
|
+
|
|
13
|
+
sDir = oFSO.GetParentFolderName(WScript.ScriptFullName)
|
|
14
|
+
|
|
15
|
+
' Get script argument or default to setup.js
|
|
16
|
+
If WScript.Arguments.Count > 0 Then
|
|
17
|
+
sScript = WScript.Arguments(0)
|
|
18
|
+
Else
|
|
19
|
+
sScript = "setup.js"
|
|
20
|
+
End If
|
|
21
|
+
|
|
22
|
+
sCmd = "/k title Sentinel AI Path (Admin) && cd /d """ & sDir & """ && echo. && echo Sentinel AI Path - Running as Administrator && echo Script: " & sScript & " && echo. && node " & sScript
|
|
23
|
+
|
|
24
|
+
' Launch elevated cmd.exe (triggers UAC once)
|
|
25
|
+
oShell.ShellExecute "cmd.exe", sCmd, sDir, "runas", 1
|