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
package/ai-path/cli.js ADDED
@@ -0,0 +1,535 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Sentinel AI Connect — CLI Entry Point
4
+ *
5
+ * Usage: npx sentinel-ai <command>
6
+ *
7
+ * Commands:
8
+ * setup Check dependencies
9
+ * wallet create Generate new wallet
10
+ * wallet balance Check P2P balance
11
+ * wallet import <mnemonic...> Import existing wallet
12
+ * connect [options] Connect to VPN
13
+ * disconnect Disconnect from VPN
14
+ * status Show connection status
15
+ * nodes [options] List available nodes
16
+ * help Show this message
17
+ */
18
+
19
+ import { readFileSync, writeFileSync, existsSync, appendFileSync } from 'fs';
20
+ import { resolve, dirname } from 'path';
21
+ import { fileURLToPath } from 'url';
22
+
23
+ const __dirname = dirname(fileURLToPath(import.meta.url));
24
+
25
+ // ─── ANSI Colors ────────────────────────────────────────────────────────────
26
+
27
+ const c = {
28
+ reset: '\x1b[0m',
29
+ bold: '\x1b[1m',
30
+ dim: '\x1b[2m',
31
+ red: '\x1b[31m',
32
+ green: '\x1b[32m',
33
+ yellow: '\x1b[33m',
34
+ blue: '\x1b[34m',
35
+ magenta: '\x1b[35m',
36
+ cyan: '\x1b[36m',
37
+ white: '\x1b[37m',
38
+ bgBlue: '\x1b[44m',
39
+ };
40
+
41
+ const ok = `${c.green}+${c.reset}`;
42
+ const warn = `${c.yellow}!${c.reset}`;
43
+ const err = `${c.red}x${c.reset}`;
44
+ const info = `${c.cyan}>${c.reset}`;
45
+
46
+ // ─── .env Loader ────────────────────────────────────────────────────────────
47
+
48
+ function loadEnv() {
49
+ const envPath = resolve(__dirname, '.env');
50
+ if (!existsSync(envPath)) return;
51
+ try {
52
+ const lines = readFileSync(envPath, 'utf-8').split('\n');
53
+ for (const line of lines) {
54
+ const trimmed = line.trim();
55
+ if (!trimmed || trimmed.startsWith('#')) continue;
56
+ const eq = trimmed.indexOf('=');
57
+ if (eq === -1) continue;
58
+ const key = trimmed.slice(0, eq).trim();
59
+ const val = trimmed.slice(eq + 1).trim();
60
+ if (!process.env[key]) {
61
+ process.env[key] = val;
62
+ }
63
+ }
64
+ } catch {
65
+ // .env read failed — non-critical
66
+ }
67
+ }
68
+
69
+ // ─── Argument Parser ────────────────────────────────────────────────────────
70
+
71
+ function parseArgs(argv) {
72
+ const args = argv.slice(2);
73
+ const command = [];
74
+ const flags = {};
75
+ const positional = [];
76
+
77
+ for (let i = 0; i < args.length; i++) {
78
+ const arg = args[i];
79
+ if (arg.startsWith('--')) {
80
+ const key = arg.slice(2);
81
+ const next = args[i + 1];
82
+ if (next && !next.startsWith('--')) {
83
+ flags[key] = next;
84
+ i++;
85
+ } else {
86
+ flags[key] = true;
87
+ }
88
+ } else {
89
+ positional.push(arg);
90
+ }
91
+ }
92
+
93
+ return { positional, flags };
94
+ }
95
+
96
+ // ─── Banner ─────────────────────────────────────────────────────────────────
97
+
98
+ function banner() {
99
+ console.log('');
100
+ console.log(`${c.bold}${c.cyan} Sentinel AI Connect${c.reset} ${c.dim}v0.2.0${c.reset}`);
101
+ console.log(`${c.dim} Decentralized VPN for AI agents${c.reset}`);
102
+ console.log('');
103
+ }
104
+
105
+ // ─── Help ───────────────────────────────────────────────────────────────────
106
+
107
+ function showHelp() {
108
+ banner();
109
+ console.log(`${c.bold}USAGE${c.reset}`);
110
+ console.log(` sentinel-ai <command> [options]`);
111
+ console.log('');
112
+ console.log(`${c.bold}COMMANDS${c.reset}`);
113
+ console.log(` ${c.cyan}setup${c.reset} Check dependencies and environment`);
114
+ console.log(` ${c.cyan}wallet create${c.reset} Generate a new wallet`);
115
+ console.log(` ${c.cyan}wallet balance${c.reset} Check P2P token balance`);
116
+ console.log(` ${c.cyan}wallet import${c.reset} <mnemonic...> Import wallet from mnemonic`);
117
+ console.log(` ${c.cyan}connect${c.reset} [options] Connect to VPN`);
118
+ console.log(` ${c.cyan}disconnect${c.reset} Disconnect from VPN`);
119
+ console.log(` ${c.cyan}status${c.reset} Show connection status`);
120
+ console.log(` ${c.cyan}nodes${c.reset} [options] List available nodes`);
121
+ console.log(` ${c.cyan}help${c.reset} Show this message`);
122
+ console.log('');
123
+ console.log(`${c.bold}CONNECT OPTIONS${c.reset}`);
124
+ console.log(` --country <code> Preferred country (e.g. US, DE, JP)`);
125
+ console.log(` --protocol <type> Protocol: wireguard or v2ray`);
126
+ console.log(` --dns <preset> DNS: google, cloudflare, or hns (Handshake)`);
127
+ console.log(` --node <address> Connect to specific node (sentnode1...)`);
128
+ console.log('');
129
+ console.log(`${c.bold}NODES OPTIONS${c.reset}`);
130
+ console.log(` --country <code> Filter by country`);
131
+ console.log(` --limit <n> Max nodes to show (default: 20)`);
132
+ console.log('');
133
+ console.log(`${c.bold}ENVIRONMENT${c.reset}`);
134
+ console.log(` MNEMONIC BIP39 mnemonic in .env file`);
135
+ console.log('');
136
+ console.log(`${c.bold}EXAMPLES${c.reset}`);
137
+ console.log(` ${c.dim}# First time setup${c.reset}`);
138
+ console.log(` sentinel-ai setup`);
139
+ console.log(` sentinel-ai wallet create`);
140
+ console.log('');
141
+ console.log(` ${c.dim}# Connect to VPN${c.reset}`);
142
+ console.log(` sentinel-ai connect`);
143
+ console.log(` sentinel-ai connect --country DE --protocol wireguard`);
144
+ console.log(` sentinel-ai connect --node sentnode1abc...`);
145
+ console.log('');
146
+ console.log(` ${c.dim}# List nodes${c.reset}`);
147
+ console.log(` sentinel-ai nodes --country US --limit 10`);
148
+ console.log('');
149
+ }
150
+
151
+ // ─── Command: setup ─────────────────────────────────────────────────────────
152
+
153
+ async function cmdSetup() {
154
+ banner();
155
+ console.log(`${info} Running environment checks...`);
156
+ console.log('');
157
+
158
+ // Delegate to setup.js which has all the detection logic
159
+ await import('./setup.js');
160
+ }
161
+
162
+ // ─── Command: wallet create ─────────────────────────────────────────────────
163
+
164
+ async function cmdWalletCreate() {
165
+ banner();
166
+ console.log(`${info} Generating new wallet...`);
167
+ console.log('');
168
+
169
+ const { createWallet } = await import('./index.js');
170
+ const wallet = await createWallet();
171
+
172
+ // Write mnemonic directly to .env — never print it to stdout
173
+ const envPath = resolve(__dirname, '.env');
174
+ const mnemonicLine = `MNEMONIC=${wallet.mnemonic}`;
175
+ if (existsSync(envPath)) {
176
+ const content = readFileSync(envPath, 'utf-8');
177
+ if (content.includes('MNEMONIC=')) {
178
+ // Replace existing MNEMONIC line
179
+ const updated = content.replace(/^MNEMONIC=.*$/m, mnemonicLine);
180
+ writeFileSync(envPath, updated, 'utf-8');
181
+ } else {
182
+ appendFileSync(envPath, `\n${mnemonicLine}\n`, 'utf-8');
183
+ }
184
+ } else {
185
+ writeFileSync(envPath, `${mnemonicLine}\n`, 'utf-8');
186
+ }
187
+
188
+ console.log(`${ok} ${c.bold}Wallet created${c.reset}`);
189
+ console.log('');
190
+ console.log(`${c.bold} Address:${c.reset} ${c.green}${wallet.address}${c.reset}`);
191
+ console.log(`${ok} Mnemonic saved to .env (24 words). ${c.red}${c.bold}NEVER share this.${c.reset}`);
192
+ console.log('');
193
+ console.log(`${info} Next steps:`);
194
+ console.log(` 1. Fund the wallet with P2P tokens`);
195
+ console.log(` 2. Connect: ${c.cyan}sentinel-ai connect${c.reset}`);
196
+ console.log('');
197
+ }
198
+
199
+ // ─── Command: wallet balance ────────────────────────────────────────────────
200
+
201
+ async function cmdWalletBalance() {
202
+ banner();
203
+
204
+ const mnemonic = process.env.MNEMONIC;
205
+ delete process.env.MNEMONIC; // Don't keep mnemonic in environment after reading
206
+ if (!mnemonic) {
207
+ console.log(`${err} No MNEMONIC in .env file.`);
208
+ console.log(` Run: ${c.cyan}sentinel-ai wallet create${c.reset}`);
209
+ console.log(` Then add the mnemonic to your .env file.`);
210
+ process.exit(1);
211
+ }
212
+
213
+ console.log(`${info} Checking balance...`);
214
+
215
+ const { getBalance } = await import('./index.js');
216
+ const bal = await getBalance(mnemonic);
217
+
218
+ console.log('');
219
+ console.log(`${ok} ${c.bold}Wallet Balance${c.reset}`);
220
+ console.log(` Address: ${c.cyan}${bal.address}${c.reset}`);
221
+ console.log(` Balance: ${c.bold}${bal.p2p}${c.reset} (${bal.udvpn.toLocaleString()} udvpn)`);
222
+ console.log(` Status: ${bal.funded ? `${c.green}Funded` : `${c.red}Insufficient`}${c.reset}`);
223
+ console.log('');
224
+
225
+ if (!bal.funded) {
226
+ console.log(`${warn} Wallet needs P2P tokens to pay for VPN sessions.`);
227
+ console.log(` Send P2P tokens to: ${c.cyan}${bal.address}${c.reset}`);
228
+ console.log('');
229
+ }
230
+ }
231
+
232
+ // ─── Command: wallet import ─────────────────────────────────────────────────
233
+
234
+ async function cmdWalletImport(words) {
235
+ banner();
236
+
237
+ if (!words || words.length === 0) {
238
+ console.log(`${err} Usage: sentinel-ai wallet import <word1 word2 word3 ...>`);
239
+ process.exit(1);
240
+ }
241
+
242
+ const mnemonic = words.join(' ');
243
+ console.log(`${info} Validating mnemonic (${words.length} words)...`);
244
+
245
+ const { importWallet } = await import('./index.js');
246
+ const result = await importWallet(mnemonic);
247
+
248
+ // Write mnemonic directly to .env — never print it to stdout
249
+ const envPath = resolve(__dirname, '.env');
250
+ const mnemonicLine = `MNEMONIC=${mnemonic}`;
251
+ if (existsSync(envPath)) {
252
+ const content = readFileSync(envPath, 'utf-8');
253
+ if (content.includes('MNEMONIC=')) {
254
+ const updated = content.replace(/^MNEMONIC=.*$/m, mnemonicLine);
255
+ writeFileSync(envPath, updated, 'utf-8');
256
+ } else {
257
+ appendFileSync(envPath, `\n${mnemonicLine}\n`, 'utf-8');
258
+ }
259
+ } else {
260
+ writeFileSync(envPath, `${mnemonicLine}\n`, 'utf-8');
261
+ }
262
+
263
+ console.log('');
264
+ console.log(`${ok} ${c.bold}Wallet imported${c.reset}`);
265
+ console.log(` Address: ${c.green}${result.address}${c.reset}`);
266
+ console.log(`${ok} Mnemonic saved to .env (${words.length} words). ${c.red}${c.bold}NEVER share this.${c.reset}`);
267
+ console.log('');
268
+ console.warn(` ${c.yellow}WARNING: Your mnemonic was passed as command arguments.${c.reset}`);
269
+ console.warn(` ${c.yellow}Clear your shell history: history -c (bash) or rm ~/.bash_history${c.reset}`);
270
+ console.log('');
271
+ }
272
+
273
+ // ─── Command: connect ───────────────────────────────────────────────────────
274
+
275
+ async function cmdConnect(flags) {
276
+ banner();
277
+
278
+ const mnemonic = process.env.MNEMONIC;
279
+ delete process.env.MNEMONIC; // Don't keep mnemonic in environment after reading
280
+ if (!mnemonic) {
281
+ console.log(`${err} No MNEMONIC in .env file.`);
282
+ console.log(` Run: ${c.cyan}sentinel-ai wallet create${c.reset}`);
283
+ process.exit(1);
284
+ }
285
+
286
+ const opts = {
287
+ mnemonic,
288
+ onProgress: (stage, detail) => {
289
+ const icon = stage === 'error' ? err : stage === 'done' ? ok : info;
290
+ console.log(` ${icon} ${c.dim}[${stage}]${c.reset} ${detail}`);
291
+ },
292
+ };
293
+
294
+ if (flags.country) opts.country = flags.country;
295
+ if (flags.protocol) opts.protocol = flags.protocol;
296
+ if (flags.dns) opts.dns = flags.dns;
297
+ if (flags.node) opts.nodeAddress = flags.node;
298
+
299
+ console.log(`${info} Connecting to Sentinel dVPN...`);
300
+ if (flags.country) console.log(` Country: ${c.cyan}${flags.country}${c.reset}`);
301
+ if (flags.protocol) console.log(` Protocol: ${c.cyan}${flags.protocol}${c.reset}`);
302
+ if (flags.dns) console.log(` DNS: ${c.cyan}${flags.dns}${c.reset}`);
303
+ if (flags.node) console.log(` Node: ${c.cyan}${flags.node}${c.reset}`);
304
+ console.log('');
305
+
306
+ const { connect, disconnect } = await import('./index.js');
307
+ const vpn = await connect(opts);
308
+
309
+ console.log('');
310
+ console.log(`${ok} ${c.bold}${c.green}Connected!${c.reset}`);
311
+ console.log(` Session: ${c.cyan}${vpn.sessionId}${c.reset}`);
312
+ console.log(` Protocol: ${c.cyan}${vpn.protocol}${c.reset}`);
313
+ console.log(` Node: ${c.cyan}${vpn.nodeAddress}${c.reset}`);
314
+ if (vpn.ip) console.log(` IP: ${c.cyan}${vpn.ip}${c.reset}`);
315
+ if (vpn.socksPort) console.log(` SOCKS5: ${c.cyan}127.0.0.1:${vpn.socksPort}${c.reset}`);
316
+ console.log('');
317
+ console.log(`${c.dim} Press Ctrl+C to disconnect${c.reset}`);
318
+ console.log('');
319
+
320
+ // Keep process alive, handle graceful shutdown
321
+ let disconnecting = false;
322
+
323
+ const cleanup = async () => {
324
+ if (disconnecting) return;
325
+ disconnecting = true;
326
+ console.log('');
327
+ console.log(`${info} Disconnecting...`);
328
+ try {
329
+ await disconnect();
330
+ console.log(`${ok} Disconnected.`);
331
+ } catch (e) {
332
+ console.log(`${warn} Disconnect error: ${e.message}`);
333
+ }
334
+ process.exit(0);
335
+ };
336
+
337
+ process.on('SIGINT', cleanup);
338
+ process.on('SIGTERM', cleanup);
339
+
340
+ // Keep alive
341
+ await new Promise(() => {});
342
+ }
343
+
344
+ // ─── Command: disconnect ────────────────────────────────────────────────────
345
+
346
+ async function cmdDisconnect() {
347
+ banner();
348
+ console.log(`${info} Disconnecting...`);
349
+
350
+ const { disconnect } = await import('./index.js');
351
+
352
+ try {
353
+ await disconnect();
354
+ console.log(`${ok} Disconnected from VPN.`);
355
+ } catch (e) {
356
+ console.log(`${warn} ${e.message}`);
357
+ }
358
+ console.log('');
359
+ }
360
+
361
+ // ─── Command: status ────────────────────────────────────────────────────────
362
+
363
+ async function cmdStatus() {
364
+ banner();
365
+
366
+ const { status } = await import('./index.js');
367
+ const s = status();
368
+
369
+ if (!s.connected) {
370
+ console.log(`${c.dim} Not connected${c.reset}`);
371
+ console.log('');
372
+ console.log(` Run: ${c.cyan}sentinel-ai connect${c.reset}`);
373
+ } else {
374
+ console.log(`${ok} ${c.bold}${c.green}VPN Active${c.reset}`);
375
+ console.log(` Session: ${c.cyan}${s.sessionId}${c.reset}`);
376
+ console.log(` Protocol: ${c.cyan}${s.protocol}${c.reset}`);
377
+ console.log(` Node: ${c.cyan}${s.nodeAddress}${c.reset}`);
378
+ console.log(` Uptime: ${c.cyan}${s.uptimeFormatted}${c.reset}`);
379
+ if (s.ip) console.log(` IP: ${c.cyan}${s.ip}${c.reset}`);
380
+ if (s.socksPort) console.log(` SOCKS5: ${c.cyan}127.0.0.1:${s.socksPort}${c.reset}`);
381
+ }
382
+ console.log('');
383
+ }
384
+
385
+ // ─── Command: nodes ─────────────────────────────────────────────────────────
386
+
387
+ async function cmdNodes(flags) {
388
+ banner();
389
+
390
+ const limit = parseInt(flags.limit, 10) || 20;
391
+ const country = flags.country || null;
392
+
393
+ console.log(`${info} Fetching online nodes...`);
394
+ if (country) console.log(` Filter: country = ${c.cyan}${country}${c.reset}`);
395
+ console.log('');
396
+
397
+ const { queryOnlineNodes, filterNodes } = await import('../index.js');
398
+
399
+ let nodes = await queryOnlineNodes({
400
+ maxNodes: 200,
401
+ onNodeProbed: ({ total, probed, online }) => {
402
+ process.stdout.write(`\r ${c.dim}Probing: ${probed}/${total} checked, ${online} online${c.reset}`);
403
+ },
404
+ });
405
+ process.stdout.write('\r' + ' '.repeat(60) + '\r'); // Clear progress line
406
+
407
+ // Filter by country if requested
408
+ if (country) {
409
+ nodes = filterNodes(nodes, { country });
410
+ }
411
+
412
+ // Limit output
413
+ const display = nodes.slice(0, limit);
414
+
415
+ if (display.length === 0) {
416
+ console.log(`${warn} No nodes found${country ? ` in "${country}"` : ''}.`);
417
+ console.log('');
418
+ return;
419
+ }
420
+
421
+ console.log(`${ok} ${c.bold}${nodes.length} nodes found${c.reset}${nodes.length > limit ? ` (showing ${limit})` : ''}`);
422
+ console.log('');
423
+
424
+ // Table header
425
+ console.log(
426
+ ` ${c.bold}${pad('#', 4)}${pad('Address', 52)}${pad('Country', 16)}${pad('Type', 12)}${pad('Score', 8)}${pad('Peers', 6)}${c.reset}`,
427
+ );
428
+ console.log(` ${c.dim}${'─'.repeat(96)}${c.reset}`);
429
+
430
+ for (let i = 0; i < display.length; i++) {
431
+ const n = display[i];
432
+ const addr = n.address || '?';
433
+ const short = addr.length > 48 ? addr.slice(0, 20) + '...' + addr.slice(-20) : addr;
434
+ const loc = n.country || n.city || '?';
435
+ const stype = n.serviceType || '?';
436
+ const score = n.qualityScore != null ? n.qualityScore.toFixed(1) : '-';
437
+ const peers = n.peers != null ? String(n.peers) : '-';
438
+
439
+ console.log(
440
+ ` ${c.dim}${pad(String(i + 1), 4)}${c.reset}${c.cyan}${pad(short, 52)}${c.reset}${pad(loc, 16)}${pad(stype, 12)}${c.green}${pad(score, 8)}${c.reset}${pad(peers, 6)}`,
441
+ );
442
+ }
443
+
444
+ console.log('');
445
+ console.log(`${info} Connect to a node: ${c.cyan}sentinel-ai connect --node <address>${c.reset}`);
446
+ console.log('');
447
+ }
448
+
449
+ /** Pad string to fixed width */
450
+ function pad(str, width) {
451
+ if (str.length >= width) return str.slice(0, width);
452
+ return str + ' '.repeat(width - str.length);
453
+ }
454
+
455
+ // ─── Main ───────────────────────────────────────────────────────────────────
456
+
457
+ async function main() {
458
+ loadEnv();
459
+
460
+ const { positional, flags } = parseArgs(process.argv);
461
+ const cmd = positional[0] || 'help';
462
+ const sub = positional[1] || '';
463
+
464
+ try {
465
+ switch (cmd) {
466
+ case 'setup':
467
+ await cmdSetup();
468
+ break;
469
+
470
+ case 'wallet':
471
+ switch (sub) {
472
+ case 'create':
473
+ await cmdWalletCreate();
474
+ break;
475
+ case 'balance':
476
+ await cmdWalletBalance();
477
+ break;
478
+ case 'import':
479
+ await cmdWalletImport(positional.slice(2));
480
+ break;
481
+ default:
482
+ console.log(`${err} Unknown wallet command: ${sub}`);
483
+ console.log(` Available: create, balance, import`);
484
+ process.exit(1);
485
+ }
486
+ break;
487
+
488
+ case 'connect':
489
+ await cmdConnect(flags);
490
+ break;
491
+
492
+ case 'disconnect':
493
+ await cmdDisconnect();
494
+ break;
495
+
496
+ case 'status':
497
+ await cmdStatus();
498
+ break;
499
+
500
+ case 'nodes':
501
+ await cmdNodes(flags);
502
+ break;
503
+
504
+ case 'help':
505
+ case '--help':
506
+ case '-h':
507
+ showHelp();
508
+ break;
509
+
510
+ default:
511
+ console.log(`${err} Unknown command: ${cmd}`);
512
+ console.log(` Run: ${c.cyan}sentinel-ai help${c.reset}`);
513
+ process.exit(1);
514
+ }
515
+ } catch (e) {
516
+ console.log('');
517
+ console.log(`${err} ${c.red}${e.message}${c.reset}`);
518
+ console.log('');
519
+
520
+ // Provide contextual recovery hints
521
+ if (e.message.includes('mnemonic') || e.message.includes('MNEMONIC')) {
522
+ console.log(`${info} Generate a wallet: ${c.cyan}sentinel-ai wallet create${c.reset}`);
523
+ console.log(`${info} Then add MNEMONIC to your .env file`);
524
+ } else if (e.message.includes('balance') || e.message.includes('Insufficient')) {
525
+ console.log(`${info} Check balance: ${c.cyan}sentinel-ai wallet balance${c.reset}`);
526
+ } else if (e.message.includes('V2Ray') || e.message.includes('WireGuard')) {
527
+ console.log(`${info} Run setup: ${c.cyan}sentinel-ai setup${c.reset}`);
528
+ }
529
+
530
+ console.log('');
531
+ process.exit(1);
532
+ }
533
+ }
534
+
535
+ main();