ardabot 0.1.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/LICENSE +14 -0
- package/dist/index.js +1161 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Copyright (c) 2025 r4v3n LLC. All rights reserved.
|
|
2
|
+
|
|
3
|
+
This software and associated documentation files (the "Software") are the
|
|
4
|
+
proprietary property of r4v3n LLC. No part of the Software may be reproduced,
|
|
5
|
+
distributed, modified, or transmitted in any form or by any means without the
|
|
6
|
+
prior written permission of r4v3n LLC.
|
|
7
|
+
|
|
8
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
9
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
10
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
11
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
12
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
13
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
14
|
+
SOFTWARE.
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,1161 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands/login.ts
|
|
7
|
+
import { createInterface } from "readline/promises";
|
|
8
|
+
import chalk from "chalk";
|
|
9
|
+
import ora from "ora";
|
|
10
|
+
|
|
11
|
+
// src/config.ts
|
|
12
|
+
import Conf from "conf";
|
|
13
|
+
var DEFAULT_API_URL = true ? "https://bot.ardabot.ai" : "https://bot.ardabot.ai";
|
|
14
|
+
var store = new Conf({
|
|
15
|
+
projectName: "arda",
|
|
16
|
+
defaults: {
|
|
17
|
+
apiUrl: DEFAULT_API_URL
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
function getApiUrl() {
|
|
21
|
+
return store.get("apiUrl");
|
|
22
|
+
}
|
|
23
|
+
function getJwt() {
|
|
24
|
+
return store.get("jwt");
|
|
25
|
+
}
|
|
26
|
+
function setJwt(jwt) {
|
|
27
|
+
store.set("jwt", jwt);
|
|
28
|
+
}
|
|
29
|
+
function getApiKey() {
|
|
30
|
+
return store.get("apiKey");
|
|
31
|
+
}
|
|
32
|
+
function setApiKey(key) {
|
|
33
|
+
store.set("apiKey", key);
|
|
34
|
+
}
|
|
35
|
+
function setInstanceId(id) {
|
|
36
|
+
store.set("instanceId", id);
|
|
37
|
+
}
|
|
38
|
+
function clearAll() {
|
|
39
|
+
store.clear();
|
|
40
|
+
store.set("apiUrl", DEFAULT_API_URL);
|
|
41
|
+
}
|
|
42
|
+
var GATE_COOKIE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
43
|
+
function getGateCookie() {
|
|
44
|
+
const cookie = store.get("gateCookie");
|
|
45
|
+
if (cookie === void 0) {
|
|
46
|
+
return void 0;
|
|
47
|
+
}
|
|
48
|
+
const match = /=(\d+)\./.exec(cookie);
|
|
49
|
+
if (match?.[1]) {
|
|
50
|
+
const ts = parseInt(match[1], 10);
|
|
51
|
+
if (!isNaN(ts) && Date.now() - ts > GATE_COOKIE_MAX_AGE_MS) {
|
|
52
|
+
store.delete("gateCookie");
|
|
53
|
+
return void 0;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return cookie;
|
|
57
|
+
}
|
|
58
|
+
function setGateCookie(cookie) {
|
|
59
|
+
store.set("gateCookie", cookie);
|
|
60
|
+
}
|
|
61
|
+
function hasCredentials() {
|
|
62
|
+
return store.get("apiKey") !== void 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/api/client.ts
|
|
66
|
+
var GateVerifyError = class extends Error {
|
|
67
|
+
status;
|
|
68
|
+
constructor(message, status) {
|
|
69
|
+
super(message);
|
|
70
|
+
this.name = "GateVerifyError";
|
|
71
|
+
this.status = status;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
var ApiClient = class {
|
|
75
|
+
baseUrl;
|
|
76
|
+
jwt;
|
|
77
|
+
apiKey;
|
|
78
|
+
gateCookie;
|
|
79
|
+
constructor(options) {
|
|
80
|
+
this.baseUrl = options?.baseUrl ?? getApiUrl();
|
|
81
|
+
this.jwt = options?.jwt ?? getJwt();
|
|
82
|
+
this.apiKey = options?.apiKey ?? getApiKey();
|
|
83
|
+
this.gateCookie = getGateCookie();
|
|
84
|
+
}
|
|
85
|
+
jwtHeaders() {
|
|
86
|
+
const headers = {
|
|
87
|
+
"Content-Type": "application/json"
|
|
88
|
+
};
|
|
89
|
+
if (this.jwt) {
|
|
90
|
+
headers["Authorization"] = `Bearer ${this.jwt}`;
|
|
91
|
+
}
|
|
92
|
+
if (this.gateCookie) {
|
|
93
|
+
headers["Cookie"] = this.gateCookie;
|
|
94
|
+
}
|
|
95
|
+
return headers;
|
|
96
|
+
}
|
|
97
|
+
apiKeyHeaders() {
|
|
98
|
+
const headers = {
|
|
99
|
+
"Content-Type": "application/json"
|
|
100
|
+
};
|
|
101
|
+
if (this.apiKey) {
|
|
102
|
+
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
103
|
+
}
|
|
104
|
+
return headers;
|
|
105
|
+
}
|
|
106
|
+
authHeaders() {
|
|
107
|
+
const headers = {
|
|
108
|
+
"Content-Type": "application/json"
|
|
109
|
+
};
|
|
110
|
+
if (this.gateCookie) {
|
|
111
|
+
headers["Cookie"] = this.gateCookie;
|
|
112
|
+
}
|
|
113
|
+
return headers;
|
|
114
|
+
}
|
|
115
|
+
// =========================================================================
|
|
116
|
+
// Gate endpoints (staging TOTP protection)
|
|
117
|
+
// =========================================================================
|
|
118
|
+
async getGateStatus() {
|
|
119
|
+
const headers = {};
|
|
120
|
+
if (this.gateCookie) {
|
|
121
|
+
headers["Cookie"] = this.gateCookie;
|
|
122
|
+
}
|
|
123
|
+
const res = await fetch(`${this.baseUrl}/gate/status`, { headers });
|
|
124
|
+
if (!res.ok) {
|
|
125
|
+
throw new Error("Failed to check gate status");
|
|
126
|
+
}
|
|
127
|
+
return await res.json();
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Verify an access code (TOTP or promo) for gate access.
|
|
131
|
+
* Returns the raw Set-Cookie header value and the verification type.
|
|
132
|
+
*/
|
|
133
|
+
async verifyGate(code) {
|
|
134
|
+
const res = await fetch(`${this.baseUrl}/gate/verify`, {
|
|
135
|
+
method: "POST",
|
|
136
|
+
headers: { "Content-Type": "application/json" },
|
|
137
|
+
body: JSON.stringify({ code }),
|
|
138
|
+
redirect: "manual"
|
|
139
|
+
});
|
|
140
|
+
if (!res.ok) {
|
|
141
|
+
const body = await res.json();
|
|
142
|
+
throw new GateVerifyError(body.error ?? "Invalid code", res.status);
|
|
143
|
+
}
|
|
144
|
+
const data = await res.json();
|
|
145
|
+
const setCookie = res.headers.get("set-cookie");
|
|
146
|
+
if (!setCookie) {
|
|
147
|
+
throw new Error("Gate verification succeeded but no session cookie received");
|
|
148
|
+
}
|
|
149
|
+
const cookieValue = setCookie.split(";")[0];
|
|
150
|
+
if (!cookieValue) {
|
|
151
|
+
throw new Error("Invalid cookie format from gate verification");
|
|
152
|
+
}
|
|
153
|
+
this.gateCookie = cookieValue;
|
|
154
|
+
const result = {
|
|
155
|
+
cookie: cookieValue,
|
|
156
|
+
type: data.type
|
|
157
|
+
};
|
|
158
|
+
if (data.promoCode !== void 0) {
|
|
159
|
+
result.promoCode = data.promoCode;
|
|
160
|
+
}
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
// =========================================================================
|
|
164
|
+
// Auth endpoints (no auth required, but may need gate cookie)
|
|
165
|
+
// =========================================================================
|
|
166
|
+
async sendSmsCode(phoneNumber) {
|
|
167
|
+
const res = await fetch(`${this.baseUrl}/api/v1/auth/sms/send`, {
|
|
168
|
+
method: "POST",
|
|
169
|
+
headers: this.authHeaders(),
|
|
170
|
+
body: JSON.stringify({ phoneNumber })
|
|
171
|
+
});
|
|
172
|
+
if (!res.ok) {
|
|
173
|
+
const err = await res.json();
|
|
174
|
+
throw new Error(err.error.message);
|
|
175
|
+
}
|
|
176
|
+
return await res.json();
|
|
177
|
+
}
|
|
178
|
+
async verifySmsCode(phoneNumber, code) {
|
|
179
|
+
const res = await fetch(`${this.baseUrl}/api/v1/auth/sms/verify`, {
|
|
180
|
+
method: "POST",
|
|
181
|
+
headers: this.authHeaders(),
|
|
182
|
+
body: JSON.stringify({ phoneNumber, code })
|
|
183
|
+
});
|
|
184
|
+
if (!res.ok) {
|
|
185
|
+
const err = await res.json();
|
|
186
|
+
if (err.error.type === "gate_required") {
|
|
187
|
+
return { gateRequired: true };
|
|
188
|
+
}
|
|
189
|
+
throw new Error(err.error.message);
|
|
190
|
+
}
|
|
191
|
+
return await res.json();
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Verify a Telegram CLI linking code.
|
|
195
|
+
* Returns a JWT token or indicates gate verification is required.
|
|
196
|
+
*/
|
|
197
|
+
async verifyTelegramLinkCode(code) {
|
|
198
|
+
const res = await fetch(`${this.baseUrl}/api/v1/auth/telegram/cli-verify`, {
|
|
199
|
+
method: "POST",
|
|
200
|
+
headers: this.authHeaders(),
|
|
201
|
+
body: JSON.stringify({ code })
|
|
202
|
+
});
|
|
203
|
+
if (!res.ok) {
|
|
204
|
+
const err = await res.json();
|
|
205
|
+
if (err.error.type === "gate_required") {
|
|
206
|
+
return { gateRequired: true };
|
|
207
|
+
}
|
|
208
|
+
throw new Error(err.error.message);
|
|
209
|
+
}
|
|
210
|
+
return await res.json();
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Create an anonymous account via CLI-only signup.
|
|
214
|
+
* Returns a JWT token or indicates gate verification is required.
|
|
215
|
+
*/
|
|
216
|
+
async cliSignup() {
|
|
217
|
+
const res = await fetch(`${this.baseUrl}/api/v1/auth/cli`, {
|
|
218
|
+
method: "POST",
|
|
219
|
+
headers: this.authHeaders()
|
|
220
|
+
});
|
|
221
|
+
if (!res.ok) {
|
|
222
|
+
const err = await res.json();
|
|
223
|
+
if (err.error.type === "gate_required") {
|
|
224
|
+
return { gateRequired: true };
|
|
225
|
+
}
|
|
226
|
+
throw new Error(err.error.message);
|
|
227
|
+
}
|
|
228
|
+
return await res.json();
|
|
229
|
+
}
|
|
230
|
+
// =========================================================================
|
|
231
|
+
// Account endpoints (JWT auth)
|
|
232
|
+
// =========================================================================
|
|
233
|
+
/**
|
|
234
|
+
* Get current account info for polling subscription/instance status.
|
|
235
|
+
*/
|
|
236
|
+
async getAccount() {
|
|
237
|
+
const res = await fetch(`${this.baseUrl}/api/v1/account`, {
|
|
238
|
+
headers: this.jwtHeaders()
|
|
239
|
+
});
|
|
240
|
+
if (!res.ok) {
|
|
241
|
+
const err = await res.json();
|
|
242
|
+
throw new Error(err.error.message);
|
|
243
|
+
}
|
|
244
|
+
return await res.json();
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Start free trial - returns Stripe checkout URL.
|
|
248
|
+
*/
|
|
249
|
+
async startTrial() {
|
|
250
|
+
const res = await fetch(`${this.baseUrl}/api/v1/account/start-trial`, {
|
|
251
|
+
method: "POST",
|
|
252
|
+
headers: this.jwtHeaders()
|
|
253
|
+
});
|
|
254
|
+
if (!res.ok) {
|
|
255
|
+
const err = await res.json();
|
|
256
|
+
throw new Error(err.error.message);
|
|
257
|
+
}
|
|
258
|
+
return await res.json();
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Redeem a promo code.
|
|
262
|
+
*/
|
|
263
|
+
async redeemPromo(code) {
|
|
264
|
+
const res = await fetch(`${this.baseUrl}/api/v1/promo/redeem`, {
|
|
265
|
+
method: "POST",
|
|
266
|
+
headers: this.jwtHeaders(),
|
|
267
|
+
body: JSON.stringify({ code })
|
|
268
|
+
});
|
|
269
|
+
if (!res.ok) {
|
|
270
|
+
const err = await res.json();
|
|
271
|
+
throw new Error(err.error.message);
|
|
272
|
+
}
|
|
273
|
+
return await res.json();
|
|
274
|
+
}
|
|
275
|
+
// =========================================================================
|
|
276
|
+
// Device management endpoints (JWT auth)
|
|
277
|
+
// =========================================================================
|
|
278
|
+
/**
|
|
279
|
+
* List linked devices (auth methods).
|
|
280
|
+
*/
|
|
281
|
+
async getDevices() {
|
|
282
|
+
const res = await fetch(`${this.baseUrl}/api/v1/account/devices`, {
|
|
283
|
+
headers: this.jwtHeaders()
|
|
284
|
+
});
|
|
285
|
+
if (!res.ok) {
|
|
286
|
+
const err = await res.json();
|
|
287
|
+
throw new Error(err.error.message);
|
|
288
|
+
}
|
|
289
|
+
const data = await res.json();
|
|
290
|
+
return data.devices;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Send SMS code for device linking.
|
|
294
|
+
*/
|
|
295
|
+
async sendDeviceSmsCode(phoneNumber) {
|
|
296
|
+
const res = await fetch(`${this.baseUrl}/api/v1/account/devices/sms/send`, {
|
|
297
|
+
method: "POST",
|
|
298
|
+
headers: this.jwtHeaders(),
|
|
299
|
+
body: JSON.stringify({ phoneNumber })
|
|
300
|
+
});
|
|
301
|
+
if (!res.ok) {
|
|
302
|
+
const err = await res.json();
|
|
303
|
+
throw new Error(err.error.message);
|
|
304
|
+
}
|
|
305
|
+
return await res.json();
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Verify SMS code and link device.
|
|
309
|
+
*/
|
|
310
|
+
async verifyDeviceSmsCode(phoneNumber, code) {
|
|
311
|
+
const res = await fetch(`${this.baseUrl}/api/v1/account/devices/sms/verify`, {
|
|
312
|
+
method: "POST",
|
|
313
|
+
headers: this.jwtHeaders(),
|
|
314
|
+
body: JSON.stringify({ phoneNumber, code })
|
|
315
|
+
});
|
|
316
|
+
if (!res.ok) {
|
|
317
|
+
const err = await res.json();
|
|
318
|
+
throw new Error(err.error.message);
|
|
319
|
+
}
|
|
320
|
+
return await res.json();
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Verify Telegram linking code and link device.
|
|
324
|
+
*/
|
|
325
|
+
async verifyDeviceTelegram(code) {
|
|
326
|
+
const res = await fetch(`${this.baseUrl}/api/v1/account/devices/telegram/verify`, {
|
|
327
|
+
method: "POST",
|
|
328
|
+
headers: this.jwtHeaders(),
|
|
329
|
+
body: JSON.stringify({ code })
|
|
330
|
+
});
|
|
331
|
+
if (!res.ok) {
|
|
332
|
+
const err = await res.json();
|
|
333
|
+
throw new Error(err.error.message);
|
|
334
|
+
}
|
|
335
|
+
return await res.json();
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Unlink a device.
|
|
339
|
+
*/
|
|
340
|
+
async unlinkDevice(id) {
|
|
341
|
+
const res = await fetch(`${this.baseUrl}/api/v1/account/devices/${id}`, {
|
|
342
|
+
method: "DELETE",
|
|
343
|
+
headers: this.jwtHeaders()
|
|
344
|
+
});
|
|
345
|
+
if (!res.ok) {
|
|
346
|
+
const err = await res.json();
|
|
347
|
+
throw new Error(err.error.message);
|
|
348
|
+
}
|
|
349
|
+
return await res.json();
|
|
350
|
+
}
|
|
351
|
+
// =========================================================================
|
|
352
|
+
// Instance endpoints (JWT auth)
|
|
353
|
+
// =========================================================================
|
|
354
|
+
async getInstance() {
|
|
355
|
+
const res = await fetch(`${this.baseUrl}/api/v1/instance`, {
|
|
356
|
+
headers: this.jwtHeaders()
|
|
357
|
+
});
|
|
358
|
+
if (res.status === 404) {
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
if (!res.ok) {
|
|
362
|
+
const err = await res.json();
|
|
363
|
+
throw new Error(err.error.message);
|
|
364
|
+
}
|
|
365
|
+
return await res.json();
|
|
366
|
+
}
|
|
367
|
+
async createApiKeyForInstance(name) {
|
|
368
|
+
const res = await fetch(`${this.baseUrl}/api/v1/instance/api-keys`, {
|
|
369
|
+
method: "POST",
|
|
370
|
+
headers: this.jwtHeaders(),
|
|
371
|
+
body: JSON.stringify({ name })
|
|
372
|
+
});
|
|
373
|
+
if (!res.ok) {
|
|
374
|
+
const err = await res.json();
|
|
375
|
+
throw new Error(err.error.message);
|
|
376
|
+
}
|
|
377
|
+
return await res.json();
|
|
378
|
+
}
|
|
379
|
+
// =========================================================================
|
|
380
|
+
// Agent endpoints (API key auth)
|
|
381
|
+
// =========================================================================
|
|
382
|
+
async getAgentStatus() {
|
|
383
|
+
const res = await fetch(`${this.baseUrl}/api/v1/agent/status`, {
|
|
384
|
+
headers: this.apiKeyHeaders()
|
|
385
|
+
});
|
|
386
|
+
if (!res.ok) {
|
|
387
|
+
const err = await res.json();
|
|
388
|
+
throw new Error(err.error.message);
|
|
389
|
+
}
|
|
390
|
+
return await res.json();
|
|
391
|
+
}
|
|
392
|
+
async getAgentSettings() {
|
|
393
|
+
const res = await fetch(`${this.baseUrl}/api/v1/agent/settings`, {
|
|
394
|
+
headers: this.apiKeyHeaders()
|
|
395
|
+
});
|
|
396
|
+
if (!res.ok) {
|
|
397
|
+
const err = await res.json();
|
|
398
|
+
throw new Error(err.error.message);
|
|
399
|
+
}
|
|
400
|
+
return await res.json();
|
|
401
|
+
}
|
|
402
|
+
async chat(message, sessionId) {
|
|
403
|
+
const res = await fetch(`${this.baseUrl}/api/v1/agent/chat`, {
|
|
404
|
+
method: "POST",
|
|
405
|
+
headers: this.apiKeyHeaders(),
|
|
406
|
+
body: JSON.stringify({ message, sessionId })
|
|
407
|
+
});
|
|
408
|
+
if (!res.ok) {
|
|
409
|
+
const err = await res.json();
|
|
410
|
+
throw new Error(err.error.message);
|
|
411
|
+
}
|
|
412
|
+
return await res.json();
|
|
413
|
+
}
|
|
414
|
+
async chatStream(message, sessionId) {
|
|
415
|
+
const res = await fetch(`${this.baseUrl}/api/v1/agent/chat/stream`, {
|
|
416
|
+
method: "POST",
|
|
417
|
+
headers: this.apiKeyHeaders(),
|
|
418
|
+
body: JSON.stringify({ message, sessionId })
|
|
419
|
+
});
|
|
420
|
+
if (!res.ok) {
|
|
421
|
+
const err = await res.json();
|
|
422
|
+
throw new Error(err.error.message);
|
|
423
|
+
}
|
|
424
|
+
if (res.body === null) {
|
|
425
|
+
throw new Error("Empty response body from stream endpoint");
|
|
426
|
+
}
|
|
427
|
+
return res.body;
|
|
428
|
+
}
|
|
429
|
+
setJwt(jwt) {
|
|
430
|
+
this.jwt = jwt;
|
|
431
|
+
}
|
|
432
|
+
setApiKey(apiKey) {
|
|
433
|
+
this.apiKey = apiKey;
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
// src/commands/login.ts
|
|
438
|
+
function prompt(rl, question) {
|
|
439
|
+
return rl.question(question);
|
|
440
|
+
}
|
|
441
|
+
async function loginCommand(options) {
|
|
442
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
443
|
+
try {
|
|
444
|
+
const baseUrl = options.url ?? getApiUrl();
|
|
445
|
+
const client = new ApiClient({ baseUrl });
|
|
446
|
+
console.log(chalk.bold("\n Welcome to Arda\n"));
|
|
447
|
+
const gateResult = await handleGateIfNeeded(rl, client);
|
|
448
|
+
if (!gateResult.passed) {
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
const promoCode = options.promo ?? gateResult.promoCode;
|
|
452
|
+
const action = await promptChoice(rl, "What would you like to do?", [
|
|
453
|
+
"Link existing account",
|
|
454
|
+
"Sign up"
|
|
455
|
+
]);
|
|
456
|
+
if (action === null) return;
|
|
457
|
+
const authOptions = action === 2 ? ["SMS", "Telegram", "CLI only"] : ["SMS", "Telegram"];
|
|
458
|
+
const authMethod = await promptChoice(rl, "How would you like to verify?", authOptions);
|
|
459
|
+
if (authMethod === null) return;
|
|
460
|
+
let jwt;
|
|
461
|
+
const isAnonymous = action === 2 && authMethod === 3;
|
|
462
|
+
if (authMethod === 1) {
|
|
463
|
+
const result = await authenticateViaSms(rl, client);
|
|
464
|
+
if (result === null) return;
|
|
465
|
+
jwt = result.jwt;
|
|
466
|
+
} else if (authMethod === 2) {
|
|
467
|
+
const result = await authenticateViaTelegram(rl, client);
|
|
468
|
+
if (result === null) return;
|
|
469
|
+
jwt = result.jwt;
|
|
470
|
+
} else {
|
|
471
|
+
const result = await authenticateViaCli(rl, client);
|
|
472
|
+
if (result === null) return;
|
|
473
|
+
jwt = result.jwt;
|
|
474
|
+
}
|
|
475
|
+
client.setJwt(jwt);
|
|
476
|
+
setJwt(jwt);
|
|
477
|
+
if (action === 1) {
|
|
478
|
+
await handleLinkExisting(client);
|
|
479
|
+
} else {
|
|
480
|
+
await handleSignUp(rl, client, promoCode, isAnonymous);
|
|
481
|
+
}
|
|
482
|
+
console.log(chalk.green("\nYou're ready to go! Run `ardabot chat` to talk to your agent.\n"));
|
|
483
|
+
} finally {
|
|
484
|
+
rl.close();
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
async function authenticateViaSms(rl, client) {
|
|
488
|
+
const phoneNumber = await prompt(rl, "Phone number (e.g. +1234567890):\n");
|
|
489
|
+
if (!phoneNumber.startsWith("+")) {
|
|
490
|
+
console.log(chalk.red("Please include your country code (e.g. +1 for US)"));
|
|
491
|
+
return null;
|
|
492
|
+
}
|
|
493
|
+
const sendSpinner = ora({ text: "Sending you a code...", discardStdin: false }).start();
|
|
494
|
+
try {
|
|
495
|
+
await client.sendSmsCode(phoneNumber);
|
|
496
|
+
sendSpinner.succeed("Code sent! Check your messages.");
|
|
497
|
+
} catch (err) {
|
|
498
|
+
sendSpinner.fail("Couldn't send the code");
|
|
499
|
+
console.log(chalk.red(err.message));
|
|
500
|
+
return null;
|
|
501
|
+
}
|
|
502
|
+
const code = await prompt(rl, "Enter the 6-digit code:\n");
|
|
503
|
+
if (!/^\d{6}$/.test(code)) {
|
|
504
|
+
console.log(chalk.red("That doesn't look right \u2014 enter the 6-digit code from your SMS."));
|
|
505
|
+
return null;
|
|
506
|
+
}
|
|
507
|
+
const verifySpinner = ora({ text: "Checking...", discardStdin: false }).start();
|
|
508
|
+
try {
|
|
509
|
+
const result = await client.verifySmsCode(phoneNumber, code);
|
|
510
|
+
if ("gateRequired" in result) {
|
|
511
|
+
verifySpinner.fail("Access verification needed");
|
|
512
|
+
console.log(chalk.yellow("This environment requires access verification for new signups."));
|
|
513
|
+
rl.resume();
|
|
514
|
+
const gateResult = await handleGateIfNeeded(rl, client, true);
|
|
515
|
+
if (!gateResult.passed) return null;
|
|
516
|
+
const retrySpinner = ora({ text: "Trying again...", discardStdin: false }).start();
|
|
517
|
+
const retryResult = await client.verifySmsCode(phoneNumber, code);
|
|
518
|
+
if ("gateRequired" in retryResult) {
|
|
519
|
+
retrySpinner.fail("Still need access verification \u2014 please try again later.");
|
|
520
|
+
return null;
|
|
521
|
+
}
|
|
522
|
+
retrySpinner.succeed(retryResult.isNew ? "Account created \u2014 welcome!" : "Welcome back!");
|
|
523
|
+
return { jwt: retryResult.token, isNew: retryResult.isNew };
|
|
524
|
+
}
|
|
525
|
+
verifySpinner.succeed(result.isNew ? "Account created \u2014 welcome!" : "Welcome back!");
|
|
526
|
+
return { jwt: result.token, isNew: result.isNew };
|
|
527
|
+
} catch (err) {
|
|
528
|
+
verifySpinner.fail("Couldn't verify that code");
|
|
529
|
+
console.log(chalk.red(err.message));
|
|
530
|
+
return null;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
async function authenticateViaTelegram(rl, client) {
|
|
534
|
+
console.log(
|
|
535
|
+
chalk.cyan("\nSend /link to @ArdaBot on Telegram, then enter the code below.\n")
|
|
536
|
+
);
|
|
537
|
+
const code = await prompt(rl, "Linking code:\n");
|
|
538
|
+
if (!/^[A-Za-z0-9]{6}$/.test(code)) {
|
|
539
|
+
console.log(chalk.red("That doesn't look right \u2014 enter the 6-character code from Telegram."));
|
|
540
|
+
return null;
|
|
541
|
+
}
|
|
542
|
+
const verifySpinner = ora({ text: "Checking...", discardStdin: false }).start();
|
|
543
|
+
try {
|
|
544
|
+
const result = await client.verifyTelegramLinkCode(code.toUpperCase());
|
|
545
|
+
if ("gateRequired" in result) {
|
|
546
|
+
verifySpinner.fail("Access verification needed");
|
|
547
|
+
console.log(chalk.yellow("This environment requires access verification for new signups."));
|
|
548
|
+
rl.resume();
|
|
549
|
+
const gateResult = await handleGateIfNeeded(rl, client, true);
|
|
550
|
+
if (!gateResult.passed) return null;
|
|
551
|
+
const retrySpinner = ora({ text: "Trying again...", discardStdin: false }).start();
|
|
552
|
+
const retryResult = await client.verifyTelegramLinkCode(code.toUpperCase());
|
|
553
|
+
if ("gateRequired" in retryResult) {
|
|
554
|
+
retrySpinner.fail("Still need access verification \u2014 please try again later.");
|
|
555
|
+
return null;
|
|
556
|
+
}
|
|
557
|
+
retrySpinner.succeed(retryResult.isNew ? "Account created \u2014 welcome!" : "Welcome back!");
|
|
558
|
+
return { jwt: retryResult.token, isNew: retryResult.isNew };
|
|
559
|
+
}
|
|
560
|
+
verifySpinner.succeed(result.isNew ? "Account created \u2014 welcome!" : "Welcome back!");
|
|
561
|
+
return { jwt: result.token, isNew: result.isNew };
|
|
562
|
+
} catch (err) {
|
|
563
|
+
verifySpinner.fail("Couldn't verify that code");
|
|
564
|
+
console.log(chalk.red(err.message));
|
|
565
|
+
return null;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
async function authenticateViaCli(rl, client) {
|
|
569
|
+
const spinner = ora({ text: "Creating account...", discardStdin: false }).start();
|
|
570
|
+
try {
|
|
571
|
+
const result = await client.cliSignup();
|
|
572
|
+
if ("gateRequired" in result) {
|
|
573
|
+
spinner.fail("Access verification needed");
|
|
574
|
+
console.log(chalk.yellow("This environment requires access verification for new signups."));
|
|
575
|
+
rl.resume();
|
|
576
|
+
const gateResult = await handleGateIfNeeded(rl, client, true);
|
|
577
|
+
if (!gateResult.passed) return null;
|
|
578
|
+
const retrySpinner = ora({ text: "Trying again...", discardStdin: false }).start();
|
|
579
|
+
const retryResult = await client.cliSignup();
|
|
580
|
+
if ("gateRequired" in retryResult) {
|
|
581
|
+
retrySpinner.fail("Still need access verification \u2014 please try again later.");
|
|
582
|
+
return null;
|
|
583
|
+
}
|
|
584
|
+
retrySpinner.succeed("Account created \u2014 welcome!");
|
|
585
|
+
return { jwt: retryResult.token };
|
|
586
|
+
}
|
|
587
|
+
spinner.succeed("Account created \u2014 welcome!");
|
|
588
|
+
return { jwt: result.token };
|
|
589
|
+
} catch (err) {
|
|
590
|
+
spinner.fail("Couldn't create account");
|
|
591
|
+
console.log(chalk.red(err.message));
|
|
592
|
+
return null;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
async function handleLinkExisting(client) {
|
|
596
|
+
const instanceSpinner = ora({ text: "Finding your agent...", discardStdin: false }).start();
|
|
597
|
+
try {
|
|
598
|
+
const instance = await client.getInstance();
|
|
599
|
+
if (instance === null) {
|
|
600
|
+
instanceSpinner.warn("No agent found. Choose Sign up instead to create one.");
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
setInstanceId(instance.id);
|
|
604
|
+
instanceSpinner.succeed(`Agent found (${instance.podStatus ?? "unknown"})`);
|
|
605
|
+
} catch (err) {
|
|
606
|
+
instanceSpinner.fail("Couldn't find your agent");
|
|
607
|
+
console.log(chalk.red(err.message));
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
await createCliApiKey(client);
|
|
611
|
+
}
|
|
612
|
+
async function handleSignUp(_rl, client, promoCode, isAnonymous = false) {
|
|
613
|
+
if (promoCode) {
|
|
614
|
+
const promoSpinner = ora({ text: "Redeeming promo code...", discardStdin: false }).start();
|
|
615
|
+
try {
|
|
616
|
+
const result = await client.redeemPromo(promoCode);
|
|
617
|
+
promoSpinner.succeed(`Promo redeemed! Pro tier until ${result.expiresAtFormatted}.`);
|
|
618
|
+
setInstanceId(result.instanceId);
|
|
619
|
+
} catch (err) {
|
|
620
|
+
promoSpinner.fail("Couldn't redeem promo code");
|
|
621
|
+
console.log(chalk.red(err.message));
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
} else {
|
|
625
|
+
const trialSpinner = ora({ text: "Starting trial...", discardStdin: false }).start();
|
|
626
|
+
try {
|
|
627
|
+
const { checkoutUrl } = await client.startTrial();
|
|
628
|
+
trialSpinner.stop();
|
|
629
|
+
console.log(chalk.bold("\nOpen this link to add a payment method and start your free trial:"));
|
|
630
|
+
console.log(chalk.cyan(` ${checkoutUrl}
|
|
631
|
+
`));
|
|
632
|
+
} catch (err) {
|
|
633
|
+
trialSpinner.fail("Couldn't start trial");
|
|
634
|
+
console.log(chalk.red(err.message));
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
const pollSpinner = ora({
|
|
638
|
+
text: "Waiting for payment... (press Ctrl+C to cancel)",
|
|
639
|
+
discardStdin: false
|
|
640
|
+
}).start();
|
|
641
|
+
const subStatus = await waitForSubscription(client);
|
|
642
|
+
if (subStatus === null) {
|
|
643
|
+
pollSpinner.fail("Payment not detected. Complete payment and run `ardabot login` to link.");
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
if (subStatus === "card_rejected") {
|
|
647
|
+
pollSpinner.fail("This card has already been used for a free trial.");
|
|
648
|
+
console.log(chalk.yellow("Please use a different card or subscribe directly."));
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
pollSpinner.succeed("Payment confirmed!");
|
|
652
|
+
}
|
|
653
|
+
const instanceSpinner = ora({ text: "Waiting for your agent to start...", discardStdin: false }).start();
|
|
654
|
+
const instanceOk = await waitForInstance(client);
|
|
655
|
+
if (!instanceOk) {
|
|
656
|
+
instanceSpinner.warn("Agent is still starting. Run `ardabot login` again once it's ready.");
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
instanceSpinner.succeed("Agent is running!");
|
|
660
|
+
await createCliApiKey(client, isAnonymous);
|
|
661
|
+
}
|
|
662
|
+
var POLL_INTERVAL_MS = 3e3;
|
|
663
|
+
var POLL_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
664
|
+
async function waitForSubscription(client) {
|
|
665
|
+
const deadline = Date.now() + POLL_TIMEOUT_MS;
|
|
666
|
+
while (Date.now() < deadline) {
|
|
667
|
+
try {
|
|
668
|
+
const account = await client.getAccount();
|
|
669
|
+
if (account.subscriptionStatus !== "signup") {
|
|
670
|
+
return account.subscriptionStatus;
|
|
671
|
+
}
|
|
672
|
+
} catch {
|
|
673
|
+
}
|
|
674
|
+
await sleep(POLL_INTERVAL_MS);
|
|
675
|
+
}
|
|
676
|
+
return null;
|
|
677
|
+
}
|
|
678
|
+
async function waitForInstance(client) {
|
|
679
|
+
const deadline = Date.now() + POLL_TIMEOUT_MS;
|
|
680
|
+
while (Date.now() < deadline) {
|
|
681
|
+
try {
|
|
682
|
+
const account = await client.getAccount();
|
|
683
|
+
if (account.instance !== null) {
|
|
684
|
+
setInstanceId(account.instance.id);
|
|
685
|
+
if (account.instance.podStatus === "running") {
|
|
686
|
+
return true;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
} catch {
|
|
690
|
+
}
|
|
691
|
+
await sleep(POLL_INTERVAL_MS);
|
|
692
|
+
}
|
|
693
|
+
return false;
|
|
694
|
+
}
|
|
695
|
+
function sleep(ms) {
|
|
696
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
697
|
+
}
|
|
698
|
+
async function createCliApiKey(client, isAnonymous = false) {
|
|
699
|
+
const keySpinner = ora({ text: "Setting up CLI access...", discardStdin: false }).start();
|
|
700
|
+
try {
|
|
701
|
+
const { key } = await client.createApiKeyForInstance("cli");
|
|
702
|
+
setApiKey(key);
|
|
703
|
+
keySpinner.succeed("All set!");
|
|
704
|
+
if (isAnonymous) {
|
|
705
|
+
console.log();
|
|
706
|
+
console.log(chalk.yellow.bold(" Important: Save your API key!"));
|
|
707
|
+
console.log(chalk.yellow(" Your account has no linked phone or Telegram."));
|
|
708
|
+
console.log(chalk.yellow(" If you lose access, run `ardabot devices` to link one."));
|
|
709
|
+
console.log();
|
|
710
|
+
console.log(` API key: ${chalk.cyan(key)}`);
|
|
711
|
+
console.log();
|
|
712
|
+
}
|
|
713
|
+
} catch (err) {
|
|
714
|
+
keySpinner.fail("Couldn't set up CLI access");
|
|
715
|
+
console.log(chalk.red(err.message));
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
async function promptChoice(rl, question, options) {
|
|
719
|
+
const optionList = options.map((o, i) => `(${i + 1}) ${o}`).join(" ");
|
|
720
|
+
const answer = await prompt(rl, `${question} ${optionList}
|
|
721
|
+
`);
|
|
722
|
+
const num = parseInt(answer.trim(), 10);
|
|
723
|
+
if (isNaN(num) || num < 1 || num > options.length) {
|
|
724
|
+
console.log(chalk.red(`Please enter a number between 1 and ${options.length}.`));
|
|
725
|
+
return null;
|
|
726
|
+
}
|
|
727
|
+
return num;
|
|
728
|
+
}
|
|
729
|
+
async function handleGateIfNeeded(rl, client, force = false) {
|
|
730
|
+
const gateSpinner = ora({ text: "Connecting...", discardStdin: false }).start();
|
|
731
|
+
try {
|
|
732
|
+
const gateStatus = await client.getGateStatus();
|
|
733
|
+
if (!gateStatus.gated) {
|
|
734
|
+
gateSpinner.succeed("Connected");
|
|
735
|
+
return { passed: true };
|
|
736
|
+
}
|
|
737
|
+
if (gateStatus.passed && !force) {
|
|
738
|
+
gateSpinner.succeed("Connected (staging)");
|
|
739
|
+
return { passed: true };
|
|
740
|
+
}
|
|
741
|
+
gateSpinner.info("We are currently in closed beta. If you have an access code, enter it now.");
|
|
742
|
+
console.log();
|
|
743
|
+
} catch {
|
|
744
|
+
gateSpinner.succeed("Connected");
|
|
745
|
+
return { passed: true };
|
|
746
|
+
}
|
|
747
|
+
const maxAttempts = 3;
|
|
748
|
+
const maxServerErrors = 2;
|
|
749
|
+
let serverErrors = 0;
|
|
750
|
+
for (let attempt = 1; attempt <= maxAttempts; ) {
|
|
751
|
+
const accessCode = await prompt(rl, "Access code:\n");
|
|
752
|
+
const trimmed = accessCode.trim();
|
|
753
|
+
if (trimmed.length === 0) {
|
|
754
|
+
console.log(chalk.red("Please enter your access code."));
|
|
755
|
+
continue;
|
|
756
|
+
}
|
|
757
|
+
const codeSpinner = ora({ text: "Verifying...", discardStdin: false }).start();
|
|
758
|
+
try {
|
|
759
|
+
const result = await client.verifyGate(trimmed);
|
|
760
|
+
setGateCookie(result.cookie);
|
|
761
|
+
if (result.type === "promo") {
|
|
762
|
+
codeSpinner.succeed("Access code accepted!");
|
|
763
|
+
const gateResult = { passed: true };
|
|
764
|
+
if (result.promoCode !== void 0) {
|
|
765
|
+
gateResult.promoCode = result.promoCode;
|
|
766
|
+
}
|
|
767
|
+
return gateResult;
|
|
768
|
+
}
|
|
769
|
+
codeSpinner.succeed("Access granted");
|
|
770
|
+
return { passed: true };
|
|
771
|
+
} catch (err) {
|
|
772
|
+
const isClientError = err instanceof GateVerifyError && err.status >= 400 && err.status < 500;
|
|
773
|
+
if (!isClientError) {
|
|
774
|
+
serverErrors++;
|
|
775
|
+
if (serverErrors >= maxServerErrors) {
|
|
776
|
+
codeSpinner.fail("Unable to verify your code right now");
|
|
777
|
+
console.log(chalk.red("Please try again later."));
|
|
778
|
+
return { passed: false };
|
|
779
|
+
}
|
|
780
|
+
codeSpinner.fail("Temporary problem verifying your code. Please try again.");
|
|
781
|
+
continue;
|
|
782
|
+
}
|
|
783
|
+
if (attempt < maxAttempts) {
|
|
784
|
+
codeSpinner.fail(`Invalid code (${attempt}/${maxAttempts} attempts)`);
|
|
785
|
+
} else {
|
|
786
|
+
codeSpinner.fail("Too many attempts");
|
|
787
|
+
console.log(chalk.red("Please wait a moment and try again."));
|
|
788
|
+
}
|
|
789
|
+
attempt++;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
return { passed: false };
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// src/commands/auth.ts
|
|
796
|
+
import chalk2 from "chalk";
|
|
797
|
+
import ora2 from "ora";
|
|
798
|
+
async function authCommand(options) {
|
|
799
|
+
const baseUrl = options.url ?? getApiUrl();
|
|
800
|
+
const client = new ApiClient({ baseUrl, apiKey: options.apiKey });
|
|
801
|
+
console.log(chalk2.bold("\n Welcome to Arda\n"));
|
|
802
|
+
const spinner = ora2("Checking your API key...").start();
|
|
803
|
+
try {
|
|
804
|
+
const status = await client.getAgentStatus();
|
|
805
|
+
setApiKey(options.apiKey);
|
|
806
|
+
setInstanceId(status.instanceId);
|
|
807
|
+
spinner.succeed(`Logged in \u2014 agent is ${status.status}`);
|
|
808
|
+
console.log(chalk2.green("\nYou're ready to go! Run `ardabot chat` to talk to your agent.\n"));
|
|
809
|
+
} catch (err) {
|
|
810
|
+
spinner.fail("That API key didn't work");
|
|
811
|
+
console.log(chalk2.red(err.message));
|
|
812
|
+
process.exitCode = 1;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// src/commands/chat.ts
|
|
817
|
+
import { createInterface as createInterface2 } from "readline/promises";
|
|
818
|
+
import chalk3 from "chalk";
|
|
819
|
+
|
|
820
|
+
// src/utils/markdown.ts
|
|
821
|
+
import { Marked } from "marked";
|
|
822
|
+
var markedInstance = null;
|
|
823
|
+
async function getMarked() {
|
|
824
|
+
if (markedInstance === null) {
|
|
825
|
+
try {
|
|
826
|
+
const { default: markedTerminal } = await import("marked-terminal");
|
|
827
|
+
const extension = markedTerminal();
|
|
828
|
+
markedInstance = new Marked(extension);
|
|
829
|
+
} catch {
|
|
830
|
+
markedInstance = new Marked();
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
return markedInstance;
|
|
834
|
+
}
|
|
835
|
+
async function renderMarkdown(text) {
|
|
836
|
+
const marked = await getMarked();
|
|
837
|
+
const rendered = marked.parse(text);
|
|
838
|
+
if (typeof rendered === "string") {
|
|
839
|
+
return rendered.replace(/\n+$/, "");
|
|
840
|
+
}
|
|
841
|
+
return text;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// src/commands/chat.ts
|
|
845
|
+
import { EventSourceParserStream } from "eventsource-parser/stream";
|
|
846
|
+
async function chatCommand(options) {
|
|
847
|
+
if (!hasCredentials()) {
|
|
848
|
+
console.log(chalk3.yellow("Not logged in. Run `ardabot login` or `ardabot auth` first."));
|
|
849
|
+
process.exitCode = 1;
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
const client = new ApiClient();
|
|
853
|
+
const sessionId = options.session ?? `cli:${Date.now()}`;
|
|
854
|
+
let botName = "agent";
|
|
855
|
+
try {
|
|
856
|
+
const settings = await client.getAgentSettings();
|
|
857
|
+
if (settings.botName) {
|
|
858
|
+
botName = settings.botName;
|
|
859
|
+
}
|
|
860
|
+
} catch {
|
|
861
|
+
}
|
|
862
|
+
console.log(chalk3.bold(`
|
|
863
|
+
${botName}`));
|
|
864
|
+
console.log(chalk3.gray(" Type a message to get started. Ctrl+C to exit.\n"));
|
|
865
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
866
|
+
rl.on("close", () => {
|
|
867
|
+
console.log(chalk3.gray("\nSee you later."));
|
|
868
|
+
process.exit(0);
|
|
869
|
+
});
|
|
870
|
+
while (true) {
|
|
871
|
+
let input;
|
|
872
|
+
try {
|
|
873
|
+
input = await rl.question(chalk3.cyan("you> "));
|
|
874
|
+
} catch {
|
|
875
|
+
break;
|
|
876
|
+
}
|
|
877
|
+
const trimmed = input.trim();
|
|
878
|
+
if (trimmed === "") {
|
|
879
|
+
continue;
|
|
880
|
+
}
|
|
881
|
+
try {
|
|
882
|
+
process.stdout.write(chalk3.magenta(`${botName}> `));
|
|
883
|
+
await streamResponse(client, trimmed, sessionId);
|
|
884
|
+
console.log("\n");
|
|
885
|
+
} catch (err) {
|
|
886
|
+
console.log();
|
|
887
|
+
console.log(chalk3.red(`Error: ${err.message}`));
|
|
888
|
+
console.log();
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
rl.close();
|
|
892
|
+
}
|
|
893
|
+
async function streamResponse(client, message, sessionId) {
|
|
894
|
+
let responseBody;
|
|
895
|
+
try {
|
|
896
|
+
responseBody = await client.chatStream(message, sessionId);
|
|
897
|
+
} catch {
|
|
898
|
+
const result = await client.chat(message, sessionId);
|
|
899
|
+
process.stdout.write(await renderMarkdown(result.response));
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
const eventStream = responseBody.pipeThrough(new TextDecoderStream()).pipeThrough(
|
|
903
|
+
new EventSourceParserStream()
|
|
904
|
+
);
|
|
905
|
+
let fullResponse = "";
|
|
906
|
+
for await (const event of eventStream) {
|
|
907
|
+
if (event.data === "[DONE]") {
|
|
908
|
+
break;
|
|
909
|
+
}
|
|
910
|
+
try {
|
|
911
|
+
const chunk = JSON.parse(event.data);
|
|
912
|
+
switch (chunk.type) {
|
|
913
|
+
case "text":
|
|
914
|
+
if (chunk.text) {
|
|
915
|
+
process.stdout.write(chunk.text);
|
|
916
|
+
fullResponse += chunk.text;
|
|
917
|
+
}
|
|
918
|
+
break;
|
|
919
|
+
case "thinking":
|
|
920
|
+
if (chunk.thinking) {
|
|
921
|
+
process.stdout.write(chalk3.gray(chunk.thinking));
|
|
922
|
+
}
|
|
923
|
+
break;
|
|
924
|
+
case "tool_start":
|
|
925
|
+
process.stdout.write(chalk3.gray("\n[tool] "));
|
|
926
|
+
break;
|
|
927
|
+
case "tool_end":
|
|
928
|
+
process.stdout.write(chalk3.gray(" done\n"));
|
|
929
|
+
break;
|
|
930
|
+
case "done":
|
|
931
|
+
if (fullResponse === "" && chunk.output?.response) {
|
|
932
|
+
process.stdout.write(await renderMarkdown(chunk.output.response));
|
|
933
|
+
}
|
|
934
|
+
break;
|
|
935
|
+
case "error":
|
|
936
|
+
if (chunk.error) {
|
|
937
|
+
process.stdout.write(chalk3.red(`
|
|
938
|
+
Error: ${chunk.error}`));
|
|
939
|
+
}
|
|
940
|
+
break;
|
|
941
|
+
}
|
|
942
|
+
} catch {
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// src/commands/status.ts
|
|
948
|
+
import chalk4 from "chalk";
|
|
949
|
+
import ora3 from "ora";
|
|
950
|
+
async function statusCommand() {
|
|
951
|
+
if (!hasCredentials()) {
|
|
952
|
+
console.log(chalk4.yellow("Not logged in. Run `ardabot login` or `ardabot auth` first."));
|
|
953
|
+
process.exitCode = 1;
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
const client = new ApiClient();
|
|
957
|
+
const spinner = ora3("Checking on your agent...").start();
|
|
958
|
+
try {
|
|
959
|
+
const status = await client.getAgentStatus();
|
|
960
|
+
spinner.stop();
|
|
961
|
+
const balance = `$${(status.balanceCents / 100).toFixed(2)}`;
|
|
962
|
+
console.log(chalk4.bold("\n Your Agent\n"));
|
|
963
|
+
console.log(` Status ${formatStatus(status.status)}`);
|
|
964
|
+
console.log(` Runtime ${status.runtimeId}`);
|
|
965
|
+
console.log(` Balance ${status.balanceCents > 0 ? chalk4.green(balance) : chalk4.red(balance)}`);
|
|
966
|
+
console.log(` Tier ${formatTier(status.tier)}`);
|
|
967
|
+
console.log(` Instance ${chalk4.gray(status.instanceId)}`);
|
|
968
|
+
console.log();
|
|
969
|
+
} catch (err) {
|
|
970
|
+
spinner.fail("Couldn't reach your agent");
|
|
971
|
+
console.log(chalk4.red(err.message));
|
|
972
|
+
process.exitCode = 1;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
function formatStatus(status) {
|
|
976
|
+
switch (status) {
|
|
977
|
+
case "running":
|
|
978
|
+
return chalk4.green("Running");
|
|
979
|
+
case "provisioning":
|
|
980
|
+
return chalk4.yellow("Starting up...");
|
|
981
|
+
case "stopped":
|
|
982
|
+
return chalk4.gray("Stopped");
|
|
983
|
+
case "error":
|
|
984
|
+
return chalk4.red("Error");
|
|
985
|
+
default:
|
|
986
|
+
return status;
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
function formatTier(tier) {
|
|
990
|
+
const tierNames = {
|
|
991
|
+
"0": "Free",
|
|
992
|
+
"1": "Pro",
|
|
993
|
+
"2": "Team",
|
|
994
|
+
"3": "Enterprise"
|
|
995
|
+
};
|
|
996
|
+
return tierNames[String(tier)] ?? String(tier);
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
// src/commands/logout.ts
|
|
1000
|
+
async function logoutCommand() {
|
|
1001
|
+
clearAll();
|
|
1002
|
+
console.log("Logged out. See you next time!");
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
// src/commands/devices.ts
|
|
1006
|
+
import { createInterface as createInterface3 } from "readline/promises";
|
|
1007
|
+
import chalk5 from "chalk";
|
|
1008
|
+
import ora4 from "ora";
|
|
1009
|
+
function prompt2(rl, question) {
|
|
1010
|
+
return rl.question(question);
|
|
1011
|
+
}
|
|
1012
|
+
async function devicesCommand(options) {
|
|
1013
|
+
if (!hasCredentials()) {
|
|
1014
|
+
console.log(chalk5.yellow("Not logged in. Run `ardabot login` first."));
|
|
1015
|
+
process.exitCode = 1;
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
1019
|
+
try {
|
|
1020
|
+
const baseUrl = options.url ?? getApiUrl();
|
|
1021
|
+
const client = new ApiClient({ baseUrl });
|
|
1022
|
+
let running = true;
|
|
1023
|
+
while (running) {
|
|
1024
|
+
const fetchSpinner = ora4({ text: "Loading devices...", discardStdin: false }).start();
|
|
1025
|
+
let devices;
|
|
1026
|
+
try {
|
|
1027
|
+
devices = await client.getDevices();
|
|
1028
|
+
fetchSpinner.stop();
|
|
1029
|
+
} catch (err) {
|
|
1030
|
+
fetchSpinner.fail("Couldn't load devices");
|
|
1031
|
+
console.log(chalk5.red(err.message));
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
console.log(chalk5.bold("\n Linked devices\n"));
|
|
1035
|
+
if (devices.length === 0) {
|
|
1036
|
+
console.log(chalk5.gray(" No devices linked.\n"));
|
|
1037
|
+
} else {
|
|
1038
|
+
for (let i = 0; i < devices.length; i++) {
|
|
1039
|
+
const d = devices[i];
|
|
1040
|
+
const typePad = d.type === "sms" ? "SMS " : "Telegram";
|
|
1041
|
+
const date = d.verifiedAt ? new Date(d.verifiedAt).toLocaleDateString("en-US", {
|
|
1042
|
+
year: "numeric",
|
|
1043
|
+
month: "short",
|
|
1044
|
+
day: "numeric"
|
|
1045
|
+
}) : "unknown";
|
|
1046
|
+
console.log(` ${i + 1}. ${typePad} ${d.identifier} ${chalk5.gray(`(linked ${date})`)}`);
|
|
1047
|
+
}
|
|
1048
|
+
console.log();
|
|
1049
|
+
}
|
|
1050
|
+
const actionList = "(1) Link new device (2) Unlink device (3) Done";
|
|
1051
|
+
const answer = await prompt2(rl, `What would you like to do? ${actionList}
|
|
1052
|
+
`);
|
|
1053
|
+
const choice = parseInt(answer.trim(), 10);
|
|
1054
|
+
switch (choice) {
|
|
1055
|
+
case 1:
|
|
1056
|
+
await linkDevice(rl, client);
|
|
1057
|
+
break;
|
|
1058
|
+
case 2:
|
|
1059
|
+
await unlinkDevice(rl, client, devices);
|
|
1060
|
+
break;
|
|
1061
|
+
case 3:
|
|
1062
|
+
running = false;
|
|
1063
|
+
break;
|
|
1064
|
+
default:
|
|
1065
|
+
console.log(chalk5.red("Please enter 1, 2, or 3."));
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
} finally {
|
|
1069
|
+
rl.close();
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
async function linkDevice(rl, client) {
|
|
1073
|
+
const typeAnswer = await prompt2(rl, "Device type? (1) SMS (2) Telegram\n");
|
|
1074
|
+
const typeChoice = parseInt(typeAnswer.trim(), 10);
|
|
1075
|
+
if (typeChoice === 1) {
|
|
1076
|
+
await linkSmsDev(rl, client);
|
|
1077
|
+
} else if (typeChoice === 2) {
|
|
1078
|
+
await linkTelegramDevice(rl, client);
|
|
1079
|
+
} else {
|
|
1080
|
+
console.log(chalk5.red("Please enter 1 or 2."));
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
async function linkSmsDev(rl, client) {
|
|
1084
|
+
const phoneNumber = await prompt2(rl, "Phone number (e.g. +1234567890):\n");
|
|
1085
|
+
if (!phoneNumber.startsWith("+")) {
|
|
1086
|
+
console.log(chalk5.red("Please include your country code (e.g. +1 for US)"));
|
|
1087
|
+
return;
|
|
1088
|
+
}
|
|
1089
|
+
const sendSpinner = ora4({ text: "Sending you a code...", discardStdin: false }).start();
|
|
1090
|
+
try {
|
|
1091
|
+
await client.sendDeviceSmsCode(phoneNumber);
|
|
1092
|
+
sendSpinner.succeed("Code sent!");
|
|
1093
|
+
} catch (err) {
|
|
1094
|
+
sendSpinner.fail("Couldn't send the code");
|
|
1095
|
+
console.log(chalk5.red(err.message));
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
const code = await prompt2(rl, "Enter the 6-digit code:\n");
|
|
1099
|
+
if (!/^\d{6}$/.test(code)) {
|
|
1100
|
+
console.log(chalk5.red("That doesn't look right \u2014 enter the 6-digit code from your SMS."));
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
const verifySpinner = ora4({ text: "Checking...", discardStdin: false }).start();
|
|
1104
|
+
try {
|
|
1105
|
+
await client.verifyDeviceSmsCode(phoneNumber, code);
|
|
1106
|
+
verifySpinner.succeed("Device linked!");
|
|
1107
|
+
} catch (err) {
|
|
1108
|
+
verifySpinner.fail("Couldn't link device");
|
|
1109
|
+
console.log(chalk5.red(err.message));
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
async function linkTelegramDevice(rl, client) {
|
|
1113
|
+
console.log(chalk5.cyan("\nSend /link to @ArdaBot on Telegram, then enter the code below.\n"));
|
|
1114
|
+
const code = await prompt2(rl, "Linking code:\n");
|
|
1115
|
+
if (!/^[A-Za-z0-9]{6}$/.test(code)) {
|
|
1116
|
+
console.log(chalk5.red("That doesn't look right \u2014 enter the 6-character code from Telegram."));
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
const verifySpinner = ora4({ text: "Checking...", discardStdin: false }).start();
|
|
1120
|
+
try {
|
|
1121
|
+
await client.verifyDeviceTelegram(code.toUpperCase());
|
|
1122
|
+
verifySpinner.succeed("Device linked!");
|
|
1123
|
+
} catch (err) {
|
|
1124
|
+
verifySpinner.fail("Couldn't link device");
|
|
1125
|
+
console.log(chalk5.red(err.message));
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
async function unlinkDevice(rl, client, devices) {
|
|
1129
|
+
if (devices.length === 0) {
|
|
1130
|
+
console.log(chalk5.yellow("No devices to unlink."));
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
const optionList = devices.map((d, i) => `(${i + 1}) ${d.type === "sms" ? "SMS" : "Telegram"} ${d.identifier}`).join(" ");
|
|
1134
|
+
const answer = await prompt2(rl, `Which device? ${optionList}
|
|
1135
|
+
`);
|
|
1136
|
+
const idx = parseInt(answer.trim(), 10) - 1;
|
|
1137
|
+
if (isNaN(idx) || idx < 0 || idx >= devices.length) {
|
|
1138
|
+
console.log(chalk5.red("Invalid selection."));
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
const device = devices[idx];
|
|
1142
|
+
const unlinkSpinner = ora4({ text: "Unlinking...", discardStdin: false }).start();
|
|
1143
|
+
try {
|
|
1144
|
+
await client.unlinkDevice(device.id);
|
|
1145
|
+
unlinkSpinner.succeed("Done!");
|
|
1146
|
+
} catch (err) {
|
|
1147
|
+
unlinkSpinner.fail("Couldn't unlink device");
|
|
1148
|
+
console.log(chalk5.red(err.message));
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
// src/index.ts
|
|
1153
|
+
var program = new Command();
|
|
1154
|
+
program.name("ardabot").description("Your AI sidekick, from the terminal.").version("0.1.0");
|
|
1155
|
+
program.command("login").description("Log in or sign up").option("--url <url>", "API base URL").option("--promo <code>", "Redeem a promo code during signup").action(loginCommand);
|
|
1156
|
+
program.command("auth").description("Log in with an API key").requiredOption("--api-key <key>", "API key (arda_sk_...)").option("--url <url>", "API base URL").action(authCommand);
|
|
1157
|
+
program.command("chat").description("Talk to your agent").option("--session <id>", "Session ID for conversation continuity").action(chatCommand);
|
|
1158
|
+
program.command("status").description("Check on your agent").action(statusCommand);
|
|
1159
|
+
program.command("devices").description("Manage linked devices (SMS, Telegram)").option("--url <url>", "API base URL").action(devicesCommand);
|
|
1160
|
+
program.command("logout").description("Log out and clear credentials").action(logoutCommand);
|
|
1161
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ardabot",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Terminal client for the Arda platform",
|
|
5
|
+
"author": "r4v3n LLC",
|
|
6
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
7
|
+
"homepage": "https://ardabot.ai",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"bin": {
|
|
10
|
+
"ardabot": "dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=18"
|
|
17
|
+
},
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsup",
|
|
23
|
+
"dev": "ARDA_DEFAULT_URL=https://staging-bot.ardabot.ai tsup --watch",
|
|
24
|
+
"typecheck": "tsc --noEmit",
|
|
25
|
+
"test": "vitest run",
|
|
26
|
+
"test:watch": "vitest",
|
|
27
|
+
"test:coverage": "vitest run --coverage",
|
|
28
|
+
"clean": "rm -rf dist"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"commander": "^12.1.0",
|
|
32
|
+
"ink": "^5.1.0",
|
|
33
|
+
"react": "^18.3.1",
|
|
34
|
+
"ink-text-input": "^6.0.0",
|
|
35
|
+
"conf": "^13.0.1",
|
|
36
|
+
"chalk": "^5.4.1",
|
|
37
|
+
"ora": "^8.1.1",
|
|
38
|
+
"marked": "^15.0.0",
|
|
39
|
+
"marked-terminal": "^7.3.0",
|
|
40
|
+
"eventsource-parser": "^3.0.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@arda/tsconfig": "workspace:*",
|
|
44
|
+
"@types/node": "^22.19.7",
|
|
45
|
+
"@types/react": "^18.3.18",
|
|
46
|
+
"tsup": "^8.3.0",
|
|
47
|
+
"typescript": "^5.9.0",
|
|
48
|
+
"vitest": "^3.0.0"
|
|
49
|
+
}
|
|
50
|
+
}
|