rightbrain-cli-alpha 0.1.0-alpha → 0.1.1-alpha
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/README.md +8 -20
- package/dist/index.js +44 -3032
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,125 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// package.json
|
|
4
|
-
var package_default = {
|
|
5
|
-
name: "rightbrain-cli-alpha",
|
|
6
|
-
version: "0.1.0-alpha",
|
|
7
|
-
description: "Command line interface for RightBrain AI",
|
|
8
|
-
type: "module",
|
|
9
|
-
main: "./dist/index.js",
|
|
10
|
-
module: "./dist/index.js",
|
|
11
|
-
types: "./dist/index.d.ts",
|
|
12
|
-
bin: {
|
|
13
|
-
rightbrain: "dist/index.js"
|
|
14
|
-
},
|
|
15
|
-
files: [
|
|
16
|
-
"dist",
|
|
17
|
-
"README.md",
|
|
18
|
-
"LICENSE"
|
|
19
|
-
],
|
|
20
|
-
publishConfig: {
|
|
21
|
-
access: "public"
|
|
22
|
-
},
|
|
23
|
-
keywords: [
|
|
24
|
-
"rightbrain",
|
|
25
|
-
"rightbrainai",
|
|
26
|
-
"ai",
|
|
27
|
-
"cli",
|
|
28
|
-
"interactive",
|
|
29
|
-
"generator"
|
|
30
|
-
],
|
|
31
|
-
scripts: {
|
|
32
|
-
clean: "rm -rf dist",
|
|
33
|
-
build: "dotenv -e ../../.env -- tsup && chmod +x dist/index.js",
|
|
34
|
-
dev: "dotenv -e ../../.env -- tsup --watch",
|
|
35
|
-
"type-check": "tsc --noEmit",
|
|
36
|
-
lint: "eslint .",
|
|
37
|
-
"link:global": "pnpm run build && pnpm link --global",
|
|
38
|
-
prepublishOnly: "pnpm run build",
|
|
39
|
-
"generate:schema": "pnpm dlx tsx scripts/generate-schema.ts"
|
|
40
|
-
},
|
|
41
|
-
dependencies: {
|
|
42
|
-
"@clack/prompts": "1.0.0-alpha.9",
|
|
43
|
-
"@rightbrain/brain-api-client": "0.0.1-dev.8.6a38b3e",
|
|
44
|
-
axios: "^1.12.0",
|
|
45
|
-
commander: "^13.1.0",
|
|
46
|
-
dotenv: "^16.5.0",
|
|
47
|
-
"dotenv-cli": "^10.0.0",
|
|
48
|
-
"dotenv-expand": "^12.0.2",
|
|
49
|
-
eslint: "^9.39.2",
|
|
50
|
-
eta: "^4.5.0",
|
|
51
|
-
"file-type": "21.3.0",
|
|
52
|
-
"just-camel-case": "^6.2.0",
|
|
53
|
-
"just-kebab-case": "^4.2.0",
|
|
54
|
-
"just-pascal-case": "^3.2.0",
|
|
55
|
-
"just-snake-case": "^3.2.0",
|
|
56
|
-
open: "^11.0.0",
|
|
57
|
-
picocolors: "^1.1.0",
|
|
58
|
-
"simple-oauth2": "^5.1.0",
|
|
59
|
-
"strip-json-comments": "^5.0.3",
|
|
60
|
-
yaml: "^2.8.0",
|
|
61
|
-
zod: "^4.3.6"
|
|
62
|
-
},
|
|
63
|
-
devDependencies: {
|
|
64
|
-
"@rightbrainai/eslint-config": "workspace:*",
|
|
65
|
-
"@rightbrainai/tsconfig": "workspace:*",
|
|
66
|
-
"@types/node": "^20.19.22",
|
|
67
|
-
"@types/simple-oauth2": "^5.0.7",
|
|
68
|
-
tsup: "^8.3.5",
|
|
69
|
-
typescript: "5.7.3"
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
// src/lib/utils.ts
|
|
74
|
-
var groupModelsByProvider = (models) => {
|
|
75
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
76
|
-
for (const model of models) {
|
|
77
|
-
const key = model.provider;
|
|
78
|
-
if (!grouped.has(key)) {
|
|
79
|
-
grouped.set(key, []);
|
|
80
|
-
}
|
|
81
|
-
grouped.get(key)?.push(model);
|
|
82
|
-
}
|
|
83
|
-
return grouped;
|
|
84
|
-
};
|
|
85
|
-
var isValidUUID = (uuid) => typeof uuid === "string" && /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/i.test(uuid);
|
|
86
|
-
function getErrorMessage(error) {
|
|
87
|
-
return error instanceof Error ? error.message : String(error);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// src/env.ts
|
|
91
|
-
import z from "zod";
|
|
92
|
-
var buildEnvSchema = z.object({
|
|
93
|
-
CLI_PUBLIC_API_URL: z.url().default("https://app.rightbrain.ai/api/v1"),
|
|
94
|
-
CLI_PUBLIC_RB_OAUTH2_CLIENT_ID: z.string().refine(isValidUUID, { message: "Invalid UUID format" }).default("ae8c4f7f-d12f-4975-98bd-e5e42ff52f8e"),
|
|
95
|
-
CLI_PUBLIC_RB_OAUTH2_URL: z.url().default("https://oauth.rightbrain.ai"),
|
|
96
|
-
CLI_PUBLIC_TEMPLATES_URL: z.url().default("https://app.rightbrain.ai/cli")
|
|
97
|
-
});
|
|
98
|
-
var buildEnv = buildEnvSchema.parse({
|
|
99
|
-
CLI_PUBLIC_API_URL: "https://stag.leftbrain.me/api/v1",
|
|
100
|
-
CLI_PUBLIC_RB_OAUTH2_CLIENT_ID: "6e50b63e-7256-4269-97ea-69eb0821fff2",
|
|
101
|
-
CLI_PUBLIC_RB_OAUTH2_URL: "https://oauth.leftbrain.me",
|
|
102
|
-
CLI_PUBLIC_TEMPLATES_URL: "https://oauth.leftbrain.me/cli"
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
// src/lib/try-catch.ts
|
|
106
|
-
function tryCatch(cb) {
|
|
107
|
-
try {
|
|
108
|
-
if (typeof cb === "function") {
|
|
109
|
-
const result = cb();
|
|
110
|
-
if (result instanceof Promise) {
|
|
111
|
-
return result.then((response) => [response, null]).catch((e) => [null, e]);
|
|
112
|
-
}
|
|
113
|
-
return [result, null];
|
|
114
|
-
}
|
|
115
|
-
return cb.then((response) => [response, null]).catch((e) => [null, e]);
|
|
116
|
-
} catch (e) {
|
|
117
|
-
return [null, e];
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// src/commands/login/pages.ts
|
|
122
|
-
var baseStyles = `
|
|
2
|
+
import Pe,{z}from'zod';import Kr from'http';import*as m from'@clack/prompts';import Ut from'dotenv';import Bt from'dotenv-expand';import te from'fs';import X from'path';import j from'picocolors';import Wr from'strip-json-comments';import Qr from'os';import wt from'crypto';import {ClientCredentials}from'simple-oauth2';import {isAxiosError}from'axios';import {inspect}from'util';import {UsersApi,Configuration,OrganizationsApi,ProjectsApi,APIKeysApi,TasksApi,ListingsApi}from'@rightbrain/brain-api-client';import yo from'open';import {parseDocument,isScalar,isMap}from'yaml';import {Eta}from'eta';import dr from'just-camel-case';import Pt from'just-kebab-case';import fr from'just-pascal-case';import ur from'just-snake-case';import {execSync}from'child_process';import {fileTypeFromBuffer}from'file-type';import {Command}from'commander';var _e={name:"rightbrain-cli-alpha",version:"0.1.1-alpha",description:"Command line interface for RightBrain AI",type:"module",main:"./dist/index.js",module:"./dist/index.js",types:"./dist/index.d.ts",bin:{rightbrain:"dist/index.js"},files:["dist","README.md","LICENSE"],publishConfig:{access:"public"},keywords:["rightbrain","rightbrainai","ai","cli","interactive","generator"],scripts:{clean:"rm -rf dist",build:"dotenv -e ../../.env -- tsup && chmod +x dist/index.js",dev:"dotenv -e ../../.env -- tsup --watch","type-check":"tsc --noEmit",lint:"eslint .","link:global":"pnpm run build && pnpm link --global",prepublishOnly:"pnpm run build","generate:schema":"pnpm dlx tsx scripts/generate-schema.ts"},dependencies:{"@clack/prompts":"1.0.0-alpha.9","@rightbrain/brain-api-client":"0.0.1-dev.8.6a38b3e",axios:"^1.12.0",commander:"^13.1.0",dotenv:"^16.5.0","dotenv-cli":"^10.0.0","dotenv-expand":"^12.0.2",eslint:"^9.39.2",eta:"^4.5.0","file-type":"21.3.0","just-camel-case":"^6.2.0","just-kebab-case":"^4.2.0","just-pascal-case":"^3.2.0","just-snake-case":"^3.2.0",open:"^11.0.0",picocolors:"^1.1.0","simple-oauth2":"^5.1.0","strip-json-comments":"^5.0.3",yaml:"^2.8.0",zod:"^4.3.6"},devDependencies:{"@rightbrainai/eslint-config":"workspace:*","@rightbrainai/tsconfig":"workspace:*","@types/node":"^20.19.22","@types/simple-oauth2":"^5.0.7",tsup:"^8.3.5",typescript:"5.7.3"}};var Ue=e=>{let t=new Map;for(let r of e){let o=r.provider;t.has(o)||t.set(o,[]),t.get(o)?.push(r);}return t},H=e=>typeof e=="string"&&/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/i.test(e);function Be(e){return e instanceof Error?e.message:String(e)}var Or=Pe.object({CLI_PUBLIC_API_URL:Pe.url().default("https://app.rightbrain.ai/api/v1"),CLI_PUBLIC_RB_OAUTH2_CLIENT_ID:Pe.string().refine(H,{message:"Invalid UUID format"}).default("ae8c4f7f-d12f-4975-98bd-e5e42ff52f8e"),CLI_PUBLIC_RB_OAUTH2_URL:Pe.url().default("https://oauth.rightbrain.ai"),CLI_PUBLIC_TEMPLATES_URL:Pe.url().default("https://app.rightbrain.ai/cli")}),b=Or.parse({CLI_PUBLIC_API_URL:"https://stag.leftbrain.me/api/v1",CLI_PUBLIC_RB_OAUTH2_CLIENT_ID:"6e50b63e-7256-4269-97ea-69eb0821fff2",CLI_PUBLIC_RB_OAUTH2_URL:"https://oauth.leftbrain.me",CLI_PUBLIC_TEMPLATES_URL:"https://stag.leftbrain.me/cli"});function p(e){try{if(typeof e=="function"){let t=e();return t instanceof Promise?t.then(r=>[r,null]).catch(r=>[null,r]):[t,null]}return e.then(t=>[t,null]).catch(t=>[null,t])}catch(t){return [null,t]}}var St=`
|
|
123
3
|
:root {
|
|
124
4
|
--bg-primary: #ffffff;
|
|
125
5
|
--bg-secondary: #f9fafb;
|
|
@@ -308,8 +188,7 @@ var baseStyles = `
|
|
|
308
188
|
color: var(--error-primary);
|
|
309
189
|
word-break: break-word;
|
|
310
190
|
}
|
|
311
|
-
|
|
312
|
-
var logoSvg = `
|
|
191
|
+
`,Lt=`
|
|
313
192
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none">
|
|
314
193
|
<path stroke="#F89013" stroke-width="2"
|
|
315
194
|
d="m12.454 3.514.009.037.001.003c.265.473.498.96.7 1.463.257.636.442 1.292.557 1.968.05.297.085.646.103 1.049" />
|
|
@@ -458,29 +337,25 @@ var logoSvg = `
|
|
|
458
337
|
<path fill="#951D89" d="M5.245 21.235a.525.525 0 1 0 0-1.05.525.525 0 0 0 0 1.05Z" />
|
|
459
338
|
<path fill="#00BDC9" d="M16.477 21.704a.524.524 0 1 0 0-1.049.524.524 0 0 0 0 1.05Z" />
|
|
460
339
|
</svg>
|
|
461
|
-
|
|
462
|
-
var successIcon = `
|
|
340
|
+
`,Nr=`
|
|
463
341
|
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
464
342
|
<circle cx="24" cy="24" r="24" fill="var(--success-primary)" fill-opacity="0.12"/>
|
|
465
343
|
<circle cx="24" cy="24" r="16" fill="var(--success-primary)"/>
|
|
466
344
|
<path d="M17 24L21.5 28.5L31 19" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
467
345
|
</svg>
|
|
468
|
-
|
|
469
|
-
var errorIcon = `
|
|
346
|
+
`,zr=`
|
|
470
347
|
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
471
348
|
<circle cx="24" cy="24" r="24" fill="var(--error-primary)" fill-opacity="0.12"/>
|
|
472
349
|
<circle cx="24" cy="24" r="16" fill="var(--error-primary)"/>
|
|
473
350
|
<path d="M19 19L29 29M29 19L19 29" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
474
351
|
</svg>
|
|
475
|
-
`;
|
|
476
|
-
function getSuccessPage() {
|
|
477
|
-
return `<!DOCTYPE html>
|
|
352
|
+
`;function Ft(){return `<!DOCTYPE html>
|
|
478
353
|
<html lang="en">
|
|
479
354
|
<head>
|
|
480
355
|
<meta charset="UTF-8">
|
|
481
356
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
482
357
|
<title>Login Successful - Rightbrain.ai</title>
|
|
483
|
-
<style>${
|
|
358
|
+
<style>${St}</style>
|
|
484
359
|
<script>
|
|
485
360
|
// Clean up the URL by removing query parameters
|
|
486
361
|
if (window.history.replaceState) {
|
|
@@ -491,12 +366,12 @@ function getSuccessPage() {
|
|
|
491
366
|
<body>
|
|
492
367
|
<div class="bg-pattern"></div>
|
|
493
368
|
<div class="logo">
|
|
494
|
-
${
|
|
369
|
+
${Lt}
|
|
495
370
|
<span class="logo-text">Rightbrain.ai</span>
|
|
496
371
|
</div>
|
|
497
372
|
<div class="container">
|
|
498
373
|
<div style="margin-bottom: 20px;">
|
|
499
|
-
${
|
|
374
|
+
${Nr}
|
|
500
375
|
</div>
|
|
501
376
|
<div class="badge">
|
|
502
377
|
<span class="badge-dot success"></span>
|
|
@@ -509,26 +384,23 @@ function getSuccessPage() {
|
|
|
509
384
|
</div>
|
|
510
385
|
</div>
|
|
511
386
|
</body>
|
|
512
|
-
</html
|
|
513
|
-
}
|
|
514
|
-
function getErrorPage(error) {
|
|
515
|
-
return `<!DOCTYPE html>
|
|
387
|
+
</html>`}function Ae(e){return `<!DOCTYPE html>
|
|
516
388
|
<html lang="en">
|
|
517
389
|
<head>
|
|
518
390
|
<meta charset="UTF-8">
|
|
519
391
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
520
392
|
<title>Login Failed - Rightbrain.ai</title>
|
|
521
|
-
<style>${
|
|
393
|
+
<style>${St}</style>
|
|
522
394
|
</head>
|
|
523
395
|
<body>
|
|
524
396
|
<div class="bg-pattern"></div>
|
|
525
397
|
<div class="logo">
|
|
526
|
-
${
|
|
398
|
+
${Lt}
|
|
527
399
|
<span class="logo-text">Rightbrain.ai</span>
|
|
528
400
|
</div>
|
|
529
401
|
<div class="container">
|
|
530
402
|
<div style="margin-bottom: 20px;">
|
|
531
|
-
${
|
|
403
|
+
${zr}
|
|
532
404
|
</div>
|
|
533
405
|
<div class="badge">
|
|
534
406
|
<span class="badge-dot error"></span>
|
|
@@ -536,2902 +408,42 @@ function getErrorPage(error) {
|
|
|
536
408
|
</div>
|
|
537
409
|
<h1 class="error">Login Failed</h1>
|
|
538
410
|
<p class="subtitle">Something went wrong during authentication. Please try again.</p>
|
|
539
|
-
<div class="error-message">${
|
|
411
|
+
<div class="error-message">${qr(e)}</div>
|
|
540
412
|
</div>
|
|
541
413
|
</body>
|
|
542
|
-
</html
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
"'": "'"
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
const body = new URLSearchParams({
|
|
568
|
-
grant_type: "authorization_code",
|
|
569
|
-
code,
|
|
570
|
-
redirect_uri: redirectUri,
|
|
571
|
-
client_id: oAuthConfig.clientId
|
|
572
|
-
});
|
|
573
|
-
if (codeVerifier) {
|
|
574
|
-
body.append("code_verifier", codeVerifier);
|
|
575
|
-
}
|
|
576
|
-
const response = await fetch(tokenUrl, {
|
|
577
|
-
method: "POST",
|
|
578
|
-
headers: {
|
|
579
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
580
|
-
},
|
|
581
|
-
body: body.toString()
|
|
582
|
-
});
|
|
583
|
-
if (!response.ok) {
|
|
584
|
-
throw new Error(`Token exchange failed: ${response.status} - ${await response.text()}`);
|
|
585
|
-
}
|
|
586
|
-
return await response.json();
|
|
587
|
-
}
|
|
588
|
-
function startCallbackServer(expectedState, codeVerifier, authUrl) {
|
|
589
|
-
return new Promise((resolve) => {
|
|
590
|
-
const server = http.createServer(async (req, res) => {
|
|
591
|
-
const url = new URL(req.url || "/", `http://localhost:${DEFAULT_PORT}`);
|
|
592
|
-
if (url.pathname === "/login") {
|
|
593
|
-
res.writeHead(302, { Location: authUrl });
|
|
594
|
-
res.end();
|
|
595
|
-
return;
|
|
596
|
-
}
|
|
597
|
-
if (url.pathname !== "/callback") {
|
|
598
|
-
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
599
|
-
res.end("Not Found");
|
|
600
|
-
return;
|
|
601
|
-
}
|
|
602
|
-
const code = url.searchParams.get("code");
|
|
603
|
-
const state = url.searchParams.get("state");
|
|
604
|
-
const error = url.searchParams.get("error");
|
|
605
|
-
const errorDescription = url.searchParams.get("error_description");
|
|
606
|
-
if (error) {
|
|
607
|
-
const errorMsg = errorDescription || error;
|
|
608
|
-
res.writeHead(400, { "Content-Type": "text/html" });
|
|
609
|
-
res.end(getErrorPage(errorMsg));
|
|
610
|
-
server.close();
|
|
611
|
-
resolve({ success: false, error: errorMsg });
|
|
612
|
-
return;
|
|
613
|
-
}
|
|
614
|
-
if (state !== expectedState) {
|
|
615
|
-
const errorMsg = "State mismatch - possible CSRF attack";
|
|
616
|
-
res.writeHead(400, { "Content-Type": "text/html" });
|
|
617
|
-
res.end(getErrorPage(errorMsg));
|
|
618
|
-
server.close();
|
|
619
|
-
resolve({ success: false, error: errorMsg });
|
|
620
|
-
return;
|
|
621
|
-
}
|
|
622
|
-
if (!code) {
|
|
623
|
-
const errorMsg = "No authorization code received";
|
|
624
|
-
res.writeHead(400, { "Content-Type": "text/html" });
|
|
625
|
-
res.end(getErrorPage(errorMsg));
|
|
626
|
-
server.close();
|
|
627
|
-
resolve({ success: false, error: errorMsg });
|
|
628
|
-
return;
|
|
629
|
-
}
|
|
630
|
-
const [token, err] = await tryCatch(() => exchangeCodeForToken(code, codeVerifier));
|
|
631
|
-
if (err || !token) {
|
|
632
|
-
const errorMsg = err instanceof Error ? err.message : "Token exchange failed";
|
|
633
|
-
res.writeHead(500, { "Content-Type": "text/html" });
|
|
634
|
-
res.end(getErrorPage(errorMsg));
|
|
635
|
-
server.close();
|
|
636
|
-
resolve({ success: false, error: errorMsg });
|
|
637
|
-
return;
|
|
638
|
-
}
|
|
639
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
640
|
-
res.end(getSuccessPage());
|
|
641
|
-
server.close();
|
|
642
|
-
resolve({ success: true, token });
|
|
643
|
-
});
|
|
644
|
-
server.listen(DEFAULT_PORT);
|
|
645
|
-
server.on("error", (err) => {
|
|
646
|
-
resolve({
|
|
647
|
-
success: false,
|
|
648
|
-
error: `Failed to start callback server on port ${DEFAULT_PORT}: ${err.message}`
|
|
649
|
-
});
|
|
650
|
-
});
|
|
651
|
-
const timeout = setTimeout(
|
|
652
|
-
() => {
|
|
653
|
-
server.close();
|
|
654
|
-
resolve({ success: false, error: "Login timeout - please try again" });
|
|
655
|
-
},
|
|
656
|
-
2 * 60 * 1e3
|
|
657
|
-
);
|
|
658
|
-
server.on("close", () => {
|
|
659
|
-
clearTimeout(timeout);
|
|
660
|
-
});
|
|
661
|
-
});
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
// src/lib/constants.ts
|
|
665
|
-
var isPiped = !process.stdout.isTTY;
|
|
666
|
-
|
|
667
|
-
// src/lib/config.ts
|
|
668
|
-
import * as p from "@clack/prompts";
|
|
669
|
-
import dotenv from "dotenv";
|
|
670
|
-
import dotenvExpand from "dotenv-expand";
|
|
671
|
-
import fs from "fs";
|
|
672
|
-
import path from "path";
|
|
673
|
-
import pc from "picocolors";
|
|
674
|
-
import stripJsonComments from "strip-json-comments";
|
|
675
|
-
import { z as z2 } from "zod";
|
|
676
|
-
var CONFIG_FILE_NAME = "rightbrain.json";
|
|
677
|
-
var DEFAULT_ENV_FILE_PATH = ".env";
|
|
678
|
-
var customConfigPath = null;
|
|
679
|
-
function setConfigPath(configPath) {
|
|
680
|
-
if (!fs.existsSync(configPath)) {
|
|
681
|
-
;
|
|
682
|
-
(isPiped ? console.error : p.outro)(pc.red(`Configuration file not found: ${configPath}`));
|
|
683
|
-
process.exit(1);
|
|
684
|
-
}
|
|
685
|
-
const stat = fs.statSync(configPath);
|
|
686
|
-
if (stat.isDirectory()) {
|
|
687
|
-
;
|
|
688
|
-
(isPiped ? console.error : p.outro)(
|
|
689
|
-
pc.red(
|
|
690
|
-
`Invalid configuration path: ${configPath}
|
|
691
|
-
Expected a file (e.g., ${CONFIG_FILE_NAME}), but received a directory.`
|
|
692
|
-
)
|
|
693
|
-
);
|
|
694
|
-
process.exit(1);
|
|
695
|
-
}
|
|
696
|
-
customConfigPath = configPath;
|
|
697
|
-
}
|
|
698
|
-
var configSchema = z2.object({
|
|
699
|
-
$schema: z2.string().optional(),
|
|
700
|
-
orgId: z2.string().refine(isValidUUID, { message: "Invalid UUID format" }),
|
|
701
|
-
projectId: z2.string().refine(isValidUUID, { message: "Invalid UUID format" }),
|
|
702
|
-
envFile: z2.string().default(DEFAULT_ENV_FILE_PATH).optional(),
|
|
703
|
-
apiKey: z2.string().optional(),
|
|
704
|
-
generate: z2.object({
|
|
705
|
-
language: z2.string(),
|
|
706
|
-
outputDir: z2.string().refine(
|
|
707
|
-
(val) => {
|
|
708
|
-
if (!val) return true;
|
|
709
|
-
const normalized = val.replace(/^\.\//, "").replace(/\/$/, "");
|
|
710
|
-
if (normalized === "" || normalized === ".") {
|
|
711
|
-
return false;
|
|
712
|
-
}
|
|
713
|
-
return true;
|
|
714
|
-
},
|
|
715
|
-
{
|
|
716
|
-
message: 'outputDir cannot be project root ("./" or "."). It must be inside the project below rightbrain.json level.'
|
|
717
|
-
}
|
|
718
|
-
),
|
|
719
|
-
taskIds: z2.array(z2.string().refine(isValidUUID, { message: "Invalid UUID format" })),
|
|
720
|
-
hardcodeTaskIds: z2.boolean().default(false).optional()
|
|
721
|
-
}).optional(),
|
|
722
|
-
clientId: z2.string().refine(isValidUUID, { message: "Invalid UUID format" }).optional(),
|
|
723
|
-
clientSecret: z2.string().optional(),
|
|
724
|
-
oauthUrl: z2.url().optional(),
|
|
725
|
-
apiUrl: z2.url().default(buildEnv.CLI_PUBLIC_API_URL).optional()
|
|
726
|
-
}).refine(
|
|
727
|
-
(data) => {
|
|
728
|
-
const hasOAuthCredentials = data.clientId && data.clientSecret;
|
|
729
|
-
const hasApiKey = !!data.apiKey;
|
|
730
|
-
return hasOAuthCredentials || hasApiKey;
|
|
731
|
-
},
|
|
732
|
-
{
|
|
733
|
-
message: "Either 'apiKey' or both 'clientId' and 'clientSecret' must be provided to authenticate"
|
|
734
|
-
}
|
|
735
|
-
);
|
|
736
|
-
function getConfigDir() {
|
|
737
|
-
if (customConfigPath) {
|
|
738
|
-
return path.dirname(customConfigPath);
|
|
739
|
-
}
|
|
740
|
-
return process.cwd();
|
|
741
|
-
}
|
|
742
|
-
function loadEnvFile(envFileName = DEFAULT_ENV_FILE_PATH, isExplicit = false) {
|
|
743
|
-
const configDir = getConfigDir();
|
|
744
|
-
const isAbsolutePath = path.isAbsolute(envFileName);
|
|
745
|
-
const envPath = isAbsolutePath ? envFileName : path.join(configDir, envFileName);
|
|
746
|
-
if (fs.existsSync(envPath)) {
|
|
747
|
-
const stat = fs.statSync(envPath);
|
|
748
|
-
if (!stat.isFile()) {
|
|
749
|
-
throw new Error(`envFile path "${envFileName}" exists but is not a file (it's a directory).`);
|
|
750
|
-
}
|
|
751
|
-
dotenvExpand.expand(dotenv.config({ path: envPath, override: true }));
|
|
752
|
-
return;
|
|
753
|
-
}
|
|
754
|
-
if (!isAbsolutePath) {
|
|
755
|
-
const envLocalPath = path.join(configDir, `${envFileName}.local`);
|
|
756
|
-
if (fs.existsSync(envLocalPath)) {
|
|
757
|
-
const stat = fs.statSync(envLocalPath);
|
|
758
|
-
if (!stat.isFile()) {
|
|
759
|
-
throw new Error(`envFile path "${envFileName}.local" exists but is not a file (it's a directory).`);
|
|
760
|
-
}
|
|
761
|
-
dotenvExpand.expand(dotenv.config({ path: envLocalPath, override: true }));
|
|
762
|
-
return;
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
if (isExplicit) {
|
|
766
|
-
const expectedPaths = isAbsolutePath ? [envPath] : [envPath, path.join(configDir, `${envFileName}.local`)];
|
|
767
|
-
throw new Error(
|
|
768
|
-
`envFile "${envFileName}" specified in config but not found. Expected at: ${expectedPaths.join(" or ")}`
|
|
769
|
-
);
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
function resolveEnvVar(value) {
|
|
773
|
-
let result = "";
|
|
774
|
-
let i = 0;
|
|
775
|
-
while (i < value.length) {
|
|
776
|
-
if (value[i] === "$" && value[i + 1] === "{") {
|
|
777
|
-
let depth = 1;
|
|
778
|
-
let j = i + 2;
|
|
779
|
-
let hasNestedVar = false;
|
|
780
|
-
while (j < value.length && depth > 0) {
|
|
781
|
-
if (value[j] === "$" && value[j + 1] === "{") {
|
|
782
|
-
hasNestedVar = true;
|
|
783
|
-
depth++;
|
|
784
|
-
j++;
|
|
785
|
-
} else if (value[j] === "}") {
|
|
786
|
-
depth--;
|
|
787
|
-
}
|
|
788
|
-
j++;
|
|
789
|
-
}
|
|
790
|
-
if (depth === 0) {
|
|
791
|
-
const varContent = value.slice(i + 2, j - 1);
|
|
792
|
-
if (hasNestedVar) {
|
|
793
|
-
throw new Error(`\${${varContent}}: bad substitution`);
|
|
794
|
-
} else {
|
|
795
|
-
const trimmedKey = varContent.trim();
|
|
796
|
-
result += process.env[trimmedKey] ?? "";
|
|
797
|
-
i = j;
|
|
798
|
-
}
|
|
799
|
-
} else {
|
|
800
|
-
result += value[i];
|
|
801
|
-
i++;
|
|
802
|
-
}
|
|
803
|
-
} else {
|
|
804
|
-
result += value[i];
|
|
805
|
-
i++;
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
return result;
|
|
809
|
-
}
|
|
810
|
-
function resolveEnvVarsDeep(obj) {
|
|
811
|
-
if (typeof obj === "string") {
|
|
812
|
-
return resolveEnvVar(obj);
|
|
813
|
-
}
|
|
814
|
-
if (Array.isArray(obj)) {
|
|
815
|
-
return obj.map(resolveEnvVarsDeep);
|
|
816
|
-
}
|
|
817
|
-
if (obj !== null && typeof obj === "object") {
|
|
818
|
-
const result = {};
|
|
819
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
820
|
-
result[key] = resolveEnvVarsDeep(value);
|
|
821
|
-
}
|
|
822
|
-
return result;
|
|
823
|
-
}
|
|
824
|
-
return obj;
|
|
825
|
-
}
|
|
826
|
-
function getConfigFilePath() {
|
|
827
|
-
if (customConfigPath) {
|
|
828
|
-
return customConfigPath;
|
|
829
|
-
}
|
|
830
|
-
const filePath = path.join(process.cwd(), CONFIG_FILE_NAME);
|
|
831
|
-
if (fs.existsSync(filePath)) {
|
|
832
|
-
return filePath;
|
|
833
|
-
}
|
|
834
|
-
customConfigPath = path.join(process.cwd(), CONFIG_FILE_NAME);
|
|
835
|
-
return customConfigPath;
|
|
836
|
-
}
|
|
837
|
-
function loadRbConfig() {
|
|
838
|
-
const configPath = getConfigFilePath();
|
|
839
|
-
if (!fs.existsSync(configPath)) {
|
|
840
|
-
return null;
|
|
841
|
-
}
|
|
842
|
-
const content = fs.readFileSync(configPath, "utf-8");
|
|
843
|
-
const rawConfig = JSON.parse(stripJsonComments(content));
|
|
844
|
-
return {
|
|
845
|
-
config: rawConfig,
|
|
846
|
-
filepath: configPath
|
|
847
|
-
};
|
|
848
|
-
}
|
|
849
|
-
var config = null;
|
|
850
|
-
function loadConfig() {
|
|
851
|
-
if (config) {
|
|
852
|
-
return config;
|
|
853
|
-
}
|
|
854
|
-
const [rbConfig, error] = tryCatch(loadRbConfig);
|
|
855
|
-
if (error) {
|
|
856
|
-
;
|
|
857
|
-
(isPiped ? console.error : p.outro)(pc.red(`Error loading configuration file: ${error}`));
|
|
858
|
-
process.exit(1);
|
|
859
|
-
}
|
|
860
|
-
if (!rbConfig) {
|
|
861
|
-
return null;
|
|
862
|
-
}
|
|
863
|
-
const rawConfig = rbConfig.config;
|
|
864
|
-
let envFileName = DEFAULT_ENV_FILE_PATH;
|
|
865
|
-
let hasExplicitEnvFile = false;
|
|
866
|
-
if (rawConfig && typeof rawConfig === "object" && rawConfig !== null && "envFile" in rawConfig && typeof rawConfig.envFile === "string") {
|
|
867
|
-
envFileName = rawConfig.envFile;
|
|
868
|
-
hasExplicitEnvFile = true;
|
|
869
|
-
}
|
|
870
|
-
const [envLoadError] = tryCatch(() => {
|
|
871
|
-
loadEnvFile(envFileName, hasExplicitEnvFile);
|
|
872
|
-
});
|
|
873
|
-
if (envLoadError) {
|
|
874
|
-
;
|
|
875
|
-
(isPiped ? console.error : p.outro)(pc.red(`Error loading env file: ${envLoadError}`));
|
|
876
|
-
process.exit(1);
|
|
877
|
-
}
|
|
878
|
-
const [resolvedConfig, envError] = tryCatch(() => resolveEnvVarsDeep(rbConfig.config));
|
|
879
|
-
if (envError) {
|
|
880
|
-
;
|
|
881
|
-
(isPiped ? console.error : p.outro)(pc.red(`Error resolving environment variables: ${envError}`));
|
|
882
|
-
process.exit(1);
|
|
883
|
-
}
|
|
884
|
-
const parsedConfig = configSchema.safeParse(resolvedConfig);
|
|
885
|
-
if (!parsedConfig.success) {
|
|
886
|
-
;
|
|
887
|
-
(isPiped ? console.error : p.log.error)("Invalid Configuration: ");
|
|
888
|
-
(isPiped ? console.error : p.log.error)(z2.prettifyError(parsedConfig.error));
|
|
889
|
-
(isPiped ? console.error : p.outro)(pc.red("Please check your configuration file and try again."));
|
|
890
|
-
process.exit(1);
|
|
891
|
-
}
|
|
892
|
-
if (parsedConfig.data.generate?.outputDir) {
|
|
893
|
-
const configDir = path.dirname(rbConfig.filepath);
|
|
894
|
-
const outputDir = parsedConfig.data.generate.outputDir;
|
|
895
|
-
const absoluteOutputDir = path.resolve(configDir, outputDir);
|
|
896
|
-
const relativeToProject = path.relative(configDir, absoluteOutputDir);
|
|
897
|
-
if (relativeToProject === "" || relativeToProject === "." || relativeToProject.startsWith("..")) {
|
|
898
|
-
;
|
|
899
|
-
(isPiped ? console.error : p.log.error)(
|
|
900
|
-
pc.red(
|
|
901
|
-
`Invalid outputDir: "${outputDir}" resolves to "${relativeToProject}" which is outside the project or at project root. outputDir must be inside the project below rightbrain.json level.`
|
|
902
|
-
)
|
|
903
|
-
);
|
|
904
|
-
process.exit(1);
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
config = { ...parsedConfig.data, filePath: rbConfig.filepath, rawConfig };
|
|
908
|
-
return config;
|
|
909
|
-
}
|
|
910
|
-
function loadConfigOrExit() {
|
|
911
|
-
const config2 = loadConfig();
|
|
912
|
-
if (!config2) {
|
|
913
|
-
;
|
|
914
|
-
(isPiped ? console.error : p.outro)(pc.red(`No configuration file found. Expected: ${CONFIG_FILE_NAME}`));
|
|
915
|
-
process.exit(1);
|
|
916
|
-
}
|
|
917
|
-
return config2;
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
// src/lib/credentials.ts
|
|
921
|
-
import fs2 from "fs";
|
|
922
|
-
import os from "os";
|
|
923
|
-
import path2 from "path";
|
|
924
|
-
var CREDENTIALS_DIR = path2.join(os.homedir(), ".rightbrain");
|
|
925
|
-
var CREDENTIALS_FILE = path2.join(CREDENTIALS_DIR, "credentials.json");
|
|
926
|
-
var credentials = null;
|
|
927
|
-
function loadCredentials() {
|
|
928
|
-
if (credentials) {
|
|
929
|
-
return credentials;
|
|
930
|
-
}
|
|
931
|
-
if (!fs2.existsSync(CREDENTIALS_FILE)) {
|
|
932
|
-
return null;
|
|
933
|
-
}
|
|
934
|
-
try {
|
|
935
|
-
const content = fs2.readFileSync(CREDENTIALS_FILE, "utf-8");
|
|
936
|
-
const credentialsFile = JSON.parse(content);
|
|
937
|
-
if (!credentialsFile.access_token) {
|
|
938
|
-
return null;
|
|
939
|
-
}
|
|
940
|
-
credentials = credentialsFile;
|
|
941
|
-
return credentials;
|
|
942
|
-
} catch {
|
|
943
|
-
return null;
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
function ensureCredentialsDir() {
|
|
947
|
-
if (!fs2.existsSync(CREDENTIALS_DIR)) {
|
|
948
|
-
fs2.mkdirSync(CREDENTIALS_DIR, { recursive: true, mode: 448 });
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
function saveCredentials(credentialsToSave, overwrite = false) {
|
|
952
|
-
ensureCredentialsDir();
|
|
953
|
-
const data = overwrite ? credentialsToSave : { ...loadCredentials(), ...credentialsToSave };
|
|
954
|
-
fs2.writeFileSync(CREDENTIALS_FILE, JSON.stringify(data, null, 2), "utf-8");
|
|
955
|
-
fs2.chmodSync(CREDENTIALS_FILE, 384);
|
|
956
|
-
credentials = data;
|
|
957
|
-
}
|
|
958
|
-
function clearCredentials() {
|
|
959
|
-
if (fs2.existsSync(CREDENTIALS_FILE)) {
|
|
960
|
-
fs2.unlinkSync(CREDENTIALS_FILE);
|
|
961
|
-
}
|
|
962
|
-
credentials = null;
|
|
963
|
-
}
|
|
964
|
-
function isTokenExpired(credentials2) {
|
|
965
|
-
if (!credentials2.expires_at) {
|
|
966
|
-
return false;
|
|
967
|
-
}
|
|
968
|
-
const bufferMs = 5 * 60 * 1e3;
|
|
969
|
-
return Date.now() >= credentials2.expires_at - bufferMs;
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
// src/lib/spinner.ts
|
|
973
|
-
var spinner = null;
|
|
974
|
-
var intervalId = null;
|
|
975
|
-
var spinnerFrames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
976
|
-
var frameIndex = 0;
|
|
977
|
-
var isTTY = process.stderr.isTTY;
|
|
978
|
-
var hideCursor = () => {
|
|
979
|
-
if (isTTY) process.stderr.write("\x1B[?25l");
|
|
980
|
-
};
|
|
981
|
-
var showCursor = () => {
|
|
982
|
-
if (isTTY) process.stderr.write("\x1B[?25h");
|
|
983
|
-
};
|
|
984
|
-
process.on("exit", showCursor);
|
|
985
|
-
process.on("SIGINT", () => {
|
|
986
|
-
showCursor();
|
|
987
|
-
process.exit(130);
|
|
988
|
-
});
|
|
989
|
-
process.on("SIGTERM", () => {
|
|
990
|
-
showCursor();
|
|
991
|
-
process.exit(143);
|
|
992
|
-
});
|
|
993
|
-
var createCustomSpinner = () => {
|
|
994
|
-
let isRunning = false;
|
|
995
|
-
return {
|
|
996
|
-
start: () => {
|
|
997
|
-
if (isRunning) {
|
|
998
|
-
return;
|
|
999
|
-
}
|
|
1000
|
-
isRunning = true;
|
|
1001
|
-
frameIndex = 0;
|
|
1002
|
-
if (intervalId) {
|
|
1003
|
-
clearInterval(intervalId);
|
|
1004
|
-
}
|
|
1005
|
-
hideCursor();
|
|
1006
|
-
process.stderr.write("\r" + spinnerFrames[frameIndex]);
|
|
1007
|
-
intervalId = setInterval(() => {
|
|
1008
|
-
frameIndex = (frameIndex + 1) % spinnerFrames.length;
|
|
1009
|
-
process.stderr.write("\r" + spinnerFrames[frameIndex]);
|
|
1010
|
-
}, 80);
|
|
1011
|
-
},
|
|
1012
|
-
stop: () => {
|
|
1013
|
-
if (intervalId) {
|
|
1014
|
-
clearInterval(intervalId);
|
|
1015
|
-
intervalId = null;
|
|
1016
|
-
}
|
|
1017
|
-
if (isRunning) {
|
|
1018
|
-
process.stderr.write("\r" + " ".repeat(spinnerFrames[0].length) + "\r");
|
|
1019
|
-
showCursor();
|
|
1020
|
-
}
|
|
1021
|
-
isRunning = false;
|
|
1022
|
-
}
|
|
1023
|
-
};
|
|
1024
|
-
};
|
|
1025
|
-
var getSpinner = () => {
|
|
1026
|
-
if (!spinner) {
|
|
1027
|
-
spinner = createCustomSpinner();
|
|
1028
|
-
}
|
|
1029
|
-
return spinner;
|
|
1030
|
-
};
|
|
1031
|
-
var stopSpinner = () => {
|
|
1032
|
-
if (spinner) {
|
|
1033
|
-
spinner.stop();
|
|
1034
|
-
spinner = null;
|
|
1035
|
-
}
|
|
1036
|
-
};
|
|
1037
|
-
|
|
1038
|
-
// src/lib/auth.ts
|
|
1039
|
-
import * as p2 from "@clack/prompts";
|
|
1040
|
-
import crypto from "crypto";
|
|
1041
|
-
import pc2 from "picocolors";
|
|
1042
|
-
import { ClientCredentials } from "simple-oauth2";
|
|
1043
|
-
async function getAuthContext(options) {
|
|
1044
|
-
const config2 = options?.loadGlobalConfig ? null : loadConfig();
|
|
1045
|
-
if (!config2) {
|
|
1046
|
-
const credentials2 = loadCredentials();
|
|
1047
|
-
if (credentials2) {
|
|
1048
|
-
if (isTokenExpired(credentials2)) {
|
|
1049
|
-
const refreshedCredentials = await refreshAccessToken(credentials2);
|
|
1050
|
-
if (refreshedCredentials) {
|
|
1051
|
-
return {
|
|
1052
|
-
accessToken: refreshedCredentials.access_token,
|
|
1053
|
-
orgId: refreshedCredentials.org_id,
|
|
1054
|
-
projectId: refreshedCredentials.project_id
|
|
1055
|
-
};
|
|
1056
|
-
}
|
|
1057
|
-
} else {
|
|
1058
|
-
return {
|
|
1059
|
-
accessToken: credentials2.access_token,
|
|
1060
|
-
orgId: credentials2.org_id,
|
|
1061
|
-
projectId: credentials2.project_id
|
|
1062
|
-
};
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
stopSpinner();
|
|
1066
|
-
(isPiped ? console.error : p2.outro)(
|
|
1067
|
-
"Not authenticated. Please run `npx rightbrain login` command to authenticate."
|
|
1068
|
-
);
|
|
1069
|
-
process.exit(1);
|
|
1070
|
-
}
|
|
1071
|
-
const accessToken2 = await getAccessTokenFromConfig(config2);
|
|
1072
|
-
return {
|
|
1073
|
-
accessToken: accessToken2,
|
|
1074
|
-
orgId: config2.orgId,
|
|
1075
|
-
projectId: config2.projectId,
|
|
1076
|
-
filePath: config2.filePath
|
|
1077
|
-
};
|
|
1078
|
-
}
|
|
1079
|
-
var accessToken = null;
|
|
1080
|
-
async function getAccessTokenFromConfig(config2) {
|
|
1081
|
-
if (config2.apiKey) {
|
|
1082
|
-
return config2.apiKey;
|
|
1083
|
-
}
|
|
1084
|
-
if (!config2.clientId || !config2.clientSecret) {
|
|
1085
|
-
stopSpinner();
|
|
1086
|
-
(isPiped ? console.error : p2.outro)(pc2.red("Client ID and secret are required to authenticate via OAuth"));
|
|
1087
|
-
process.exit(1);
|
|
1088
|
-
}
|
|
1089
|
-
if (!accessToken || accessToken.expired()) {
|
|
1090
|
-
const client = new ClientCredentials({
|
|
1091
|
-
auth: {
|
|
1092
|
-
tokenHost: config2.oauthUrl ?? buildEnv.CLI_PUBLIC_RB_OAUTH2_URL,
|
|
1093
|
-
tokenPath: "/oauth2/token"
|
|
1094
|
-
},
|
|
1095
|
-
client: {
|
|
1096
|
-
id: config2.clientId,
|
|
1097
|
-
secret: config2.clientSecret
|
|
1098
|
-
},
|
|
1099
|
-
http: { json: "force" }
|
|
1100
|
-
});
|
|
1101
|
-
const [token, tokenError] = await tryCatch(() => client.getToken({}));
|
|
1102
|
-
if (tokenError || !token?.token.access_token) {
|
|
1103
|
-
stopSpinner();
|
|
1104
|
-
(isPiped ? console.error : p2.outro)(pc2.red("Failed to obtain access token"));
|
|
1105
|
-
process.exit(1);
|
|
1106
|
-
}
|
|
1107
|
-
accessToken = token;
|
|
1108
|
-
return token.token.access_token;
|
|
1109
|
-
}
|
|
1110
|
-
return accessToken.token.access_token;
|
|
1111
|
-
}
|
|
1112
|
-
var oAuthConfig = {
|
|
1113
|
-
oauthUrl: buildEnv.CLI_PUBLIC_RB_OAUTH2_URL,
|
|
1114
|
-
authPath: "/oauth2/auth",
|
|
1115
|
-
tokenPath: "/oauth2/token",
|
|
1116
|
-
clientId: buildEnv.CLI_PUBLIC_RB_OAUTH2_CLIENT_ID
|
|
1117
|
-
};
|
|
1118
|
-
async function refreshAccessToken(credentials2) {
|
|
1119
|
-
if (!credentials2.refresh_token) {
|
|
1120
|
-
return null;
|
|
1121
|
-
}
|
|
1122
|
-
const tokenUrl = `${oAuthConfig.oauthUrl}${oAuthConfig.tokenPath}`;
|
|
1123
|
-
const body = new URLSearchParams({
|
|
1124
|
-
grant_type: "refresh_token",
|
|
1125
|
-
refresh_token: credentials2.refresh_token,
|
|
1126
|
-
client_id: oAuthConfig.clientId
|
|
1127
|
-
});
|
|
1128
|
-
try {
|
|
1129
|
-
const response = await fetch(tokenUrl, {
|
|
1130
|
-
method: "POST",
|
|
1131
|
-
headers: {
|
|
1132
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
1133
|
-
},
|
|
1134
|
-
body: body.toString()
|
|
1135
|
-
});
|
|
1136
|
-
if (!response.ok) {
|
|
1137
|
-
return null;
|
|
1138
|
-
}
|
|
1139
|
-
const tokenResponse = await response.json();
|
|
1140
|
-
const expiresAt = tokenResponse.expires_in ? Date.now() + tokenResponse.expires_in * 1e3 : void 0;
|
|
1141
|
-
const refreshedCredentials = {
|
|
1142
|
-
...credentials2,
|
|
1143
|
-
access_token: tokenResponse.access_token,
|
|
1144
|
-
refresh_token: tokenResponse.refresh_token ?? credentials2.refresh_token,
|
|
1145
|
-
expires_at: expiresAt
|
|
1146
|
-
};
|
|
1147
|
-
saveCredentials(refreshedCredentials, true);
|
|
1148
|
-
return refreshedCredentials;
|
|
1149
|
-
} catch {
|
|
1150
|
-
return null;
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
function generateCodeVerifier() {
|
|
1154
|
-
return crypto.randomBytes(32).toString("base64url");
|
|
1155
|
-
}
|
|
1156
|
-
async function generateCodeChallenge(verifier) {
|
|
1157
|
-
const hash = crypto.createHash("sha256").update(verifier).digest();
|
|
1158
|
-
return hash.toString("base64url");
|
|
1159
|
-
}
|
|
1160
|
-
function getAuthorizationUrl(state, codeChallenge) {
|
|
1161
|
-
const redirectUri = getRedirectUri();
|
|
1162
|
-
const params = new URLSearchParams({
|
|
1163
|
-
client_id: oAuthConfig.clientId,
|
|
1164
|
-
redirect_uri: redirectUri,
|
|
1165
|
-
response_type: "code",
|
|
1166
|
-
scope: "offline_access",
|
|
1167
|
-
state
|
|
1168
|
-
});
|
|
1169
|
-
if (codeChallenge) {
|
|
1170
|
-
params.append("code_challenge", codeChallenge);
|
|
1171
|
-
params.append("code_challenge_method", "S256");
|
|
1172
|
-
}
|
|
1173
|
-
return `${oAuthConfig.oauthUrl}${oAuthConfig.authPath}?${params.toString()}`;
|
|
1174
|
-
}
|
|
1175
|
-
function generateState() {
|
|
1176
|
-
return crypto.randomBytes(32).toString("hex");
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
// src/lib/handle-api-error.ts
|
|
1180
|
-
import * as p3 from "@clack/prompts";
|
|
1181
|
-
import { isAxiosError } from "axios";
|
|
1182
|
-
import { inspect } from "util";
|
|
1183
|
-
function handleApiError(error) {
|
|
1184
|
-
if (!isPiped) {
|
|
1185
|
-
p3.outro("Something went wrong");
|
|
1186
|
-
}
|
|
1187
|
-
if (isAxiosError(error)) {
|
|
1188
|
-
const response = error.response;
|
|
1189
|
-
if (response?.data) {
|
|
1190
|
-
if (response.status === 401) {
|
|
1191
|
-
console.error("Unauthorized Access please login again.");
|
|
1192
|
-
}
|
|
1193
|
-
console.error("Error:");
|
|
1194
|
-
if (isPiped) {
|
|
1195
|
-
console.info(JSON.stringify(response.data, null, 2));
|
|
1196
|
-
} else {
|
|
1197
|
-
console.info(inspect(response.data, { depth: null, colors: true }));
|
|
1198
|
-
}
|
|
1199
|
-
return;
|
|
1200
|
-
}
|
|
1201
|
-
if (response?.status) {
|
|
1202
|
-
console.error(`Error: Request failed with status ${response.status}`);
|
|
1203
|
-
return;
|
|
1204
|
-
}
|
|
1205
|
-
}
|
|
1206
|
-
if (error instanceof Error) {
|
|
1207
|
-
console.error(error.message);
|
|
1208
|
-
return;
|
|
1209
|
-
}
|
|
1210
|
-
;
|
|
1211
|
-
(isPiped ? console.error : p3.outro)("An unexpected error occurred");
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
// src/lib/select-project.ts
|
|
1215
|
-
import * as p4 from "@clack/prompts";
|
|
1216
|
-
import { OrganizationsApi, ProjectsApi } from "@rightbrain/brain-api-client";
|
|
1217
|
-
import pc3 from "picocolors";
|
|
1218
|
-
async function selectProject({ accessToken: accessToken2, orgId, projectId }) {
|
|
1219
|
-
const spinner13 = getSpinner();
|
|
1220
|
-
if (!orgId) {
|
|
1221
|
-
spinner13.start();
|
|
1222
|
-
const [organizations, orgError] = await tryCatch(async () => {
|
|
1223
|
-
const orgApi = new OrganizationsApi(getConfiguration(accessToken2));
|
|
1224
|
-
return await orgApi.listOrganizations().then((response) => response.data.results);
|
|
1225
|
-
});
|
|
1226
|
-
if (orgError || !organizations) {
|
|
1227
|
-
spinner13.stop();
|
|
1228
|
-
handleApiError(orgError);
|
|
1229
|
-
process.exit(1);
|
|
1230
|
-
}
|
|
1231
|
-
spinner13.stop();
|
|
1232
|
-
if (organizations.length === 0) {
|
|
1233
|
-
p4.cancel("No organizations found. Please create one first.");
|
|
1234
|
-
process.exit(1);
|
|
1235
|
-
}
|
|
1236
|
-
if (organizations.length === 1) {
|
|
1237
|
-
orgId = organizations[0].id;
|
|
1238
|
-
p4.log.info(`Using organization: ${pc3.cyan(organizations[0].name)}`);
|
|
1239
|
-
} else {
|
|
1240
|
-
const selectedOrg = await p4.select({
|
|
1241
|
-
message: "Select an organization",
|
|
1242
|
-
options: organizations.map((org) => ({
|
|
1243
|
-
value: org.id,
|
|
1244
|
-
label: org.name,
|
|
1245
|
-
hint: org.id
|
|
1246
|
-
}))
|
|
1247
|
-
});
|
|
1248
|
-
if (p4.isCancel(selectedOrg)) {
|
|
1249
|
-
p4.outro("Select project cancelled");
|
|
1250
|
-
process.exit(0);
|
|
1251
|
-
}
|
|
1252
|
-
orgId = selectedOrg;
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
if (!projectId) {
|
|
1256
|
-
spinner13.start();
|
|
1257
|
-
const [projects, projectError] = await tryCatch(async () => {
|
|
1258
|
-
const projectApi = new ProjectsApi(getConfiguration(accessToken2));
|
|
1259
|
-
return await projectApi.listProjects(orgId).then((response) => response.data.results);
|
|
1260
|
-
});
|
|
1261
|
-
if (projectError || !projects) {
|
|
1262
|
-
spinner13.stop();
|
|
1263
|
-
handleApiError(projectError);
|
|
1264
|
-
process.exit(1);
|
|
1265
|
-
}
|
|
1266
|
-
spinner13.stop();
|
|
1267
|
-
if (projects.length === 0) {
|
|
1268
|
-
p4.cancel("No projects found. Please create one first.");
|
|
1269
|
-
process.exit(1);
|
|
1270
|
-
}
|
|
1271
|
-
if (projects.length === 1) {
|
|
1272
|
-
projectId = projects[0].id;
|
|
1273
|
-
p4.log.info(`Using project: ${pc3.cyan(projects[0].name)}`);
|
|
1274
|
-
} else {
|
|
1275
|
-
const selectedProject = await p4.select({
|
|
1276
|
-
message: "Select a project",
|
|
1277
|
-
options: projects.map((project) => ({
|
|
1278
|
-
value: project.id,
|
|
1279
|
-
label: project.name,
|
|
1280
|
-
hint: project.id
|
|
1281
|
-
}))
|
|
1282
|
-
});
|
|
1283
|
-
if (p4.isCancel(selectedProject)) {
|
|
1284
|
-
p4.outro("Select project cancelled");
|
|
1285
|
-
process.exit(0);
|
|
1286
|
-
}
|
|
1287
|
-
projectId = selectedProject;
|
|
1288
|
-
}
|
|
1289
|
-
}
|
|
1290
|
-
if (typeof orgId !== "string" || typeof projectId !== "string") {
|
|
1291
|
-
p4.cancel("Failed to select project");
|
|
1292
|
-
process.exit(1);
|
|
1293
|
-
}
|
|
1294
|
-
return { orgId, projectId };
|
|
1295
|
-
}
|
|
1296
|
-
|
|
1297
|
-
// src/lib/client.ts
|
|
1298
|
-
import * as p5 from "@clack/prompts";
|
|
1299
|
-
import {
|
|
1300
|
-
APIKeysApi,
|
|
1301
|
-
Configuration,
|
|
1302
|
-
ListingsApi,
|
|
1303
|
-
OrganizationsApi as OrganizationsApi2,
|
|
1304
|
-
ProjectsApi as ProjectsApi2,
|
|
1305
|
-
TasksApi
|
|
1306
|
-
} from "@rightbrain/brain-api-client";
|
|
1307
|
-
var getConfiguration = (accessToken2) => {
|
|
1308
|
-
return new Configuration({
|
|
1309
|
-
basePath: buildEnv.CLI_PUBLIC_API_URL,
|
|
1310
|
-
accessToken: accessToken2
|
|
1311
|
-
});
|
|
1312
|
-
};
|
|
1313
|
-
var ApiClient = class _ApiClient {
|
|
1314
|
-
_options;
|
|
1315
|
-
tasksApi;
|
|
1316
|
-
listingsApi;
|
|
1317
|
-
organizationsApi;
|
|
1318
|
-
projectsApi;
|
|
1319
|
-
apiKeysApi;
|
|
1320
|
-
constructor(options) {
|
|
1321
|
-
this._options = options;
|
|
1322
|
-
const apiConfig = getConfiguration(options.accessToken);
|
|
1323
|
-
this.tasksApi = new TasksApi(apiConfig);
|
|
1324
|
-
this.listingsApi = new ListingsApi(apiConfig);
|
|
1325
|
-
this.organizationsApi = new OrganizationsApi2(apiConfig);
|
|
1326
|
-
this.projectsApi = new ProjectsApi2(apiConfig);
|
|
1327
|
-
this.apiKeysApi = new APIKeysApi(apiConfig);
|
|
1328
|
-
}
|
|
1329
|
-
get orgId() {
|
|
1330
|
-
return this._options.orgId;
|
|
1331
|
-
}
|
|
1332
|
-
get projectId() {
|
|
1333
|
-
return this._options.projectId;
|
|
1334
|
-
}
|
|
1335
|
-
getTaskUrl(taskId) {
|
|
1336
|
-
return `${buildEnv.CLI_PUBLIC_API_URL}/org/${this.orgId}/project/${this.projectId}/task/${taskId}`;
|
|
1337
|
-
}
|
|
1338
|
-
static async fromAuthContext() {
|
|
1339
|
-
const context = await getAuthContext();
|
|
1340
|
-
const selectedProject = await selectProject(context);
|
|
1341
|
-
if (!context.orgId || !context.projectId) {
|
|
1342
|
-
saveCredentials({
|
|
1343
|
-
access_token: context.accessToken,
|
|
1344
|
-
org_id: selectedProject.orgId,
|
|
1345
|
-
project_id: selectedProject.projectId
|
|
1346
|
-
});
|
|
1347
|
-
}
|
|
1348
|
-
return new _ApiClient({
|
|
1349
|
-
...context,
|
|
1350
|
-
...selectedProject
|
|
1351
|
-
});
|
|
1352
|
-
}
|
|
1353
|
-
static async fromConfig() {
|
|
1354
|
-
const context = await getAuthContext();
|
|
1355
|
-
if (!context.orgId || !context.projectId) {
|
|
1356
|
-
;
|
|
1357
|
-
(isPiped ? console.error : p5.outro)("orgId and projectId are required to authenticate.");
|
|
1358
|
-
process.exit(1);
|
|
1359
|
-
}
|
|
1360
|
-
return new _ApiClient({ accessToken: context.accessToken, orgId: context.orgId, projectId: context.projectId });
|
|
1361
|
-
}
|
|
1362
|
-
async listModels() {
|
|
1363
|
-
const response = await this.listingsApi.getAllModels(this.orgId, this.projectId);
|
|
1364
|
-
return response.data;
|
|
1365
|
-
}
|
|
1366
|
-
async createTask(payload) {
|
|
1367
|
-
const response = await this.tasksApi.createTask(this.orgId, this.projectId, payload);
|
|
1368
|
-
return response.data;
|
|
1369
|
-
}
|
|
1370
|
-
async getOrganization(orgId) {
|
|
1371
|
-
const response = await this.organizationsApi.getOrganization(orgId);
|
|
1372
|
-
return response.data;
|
|
1373
|
-
}
|
|
1374
|
-
async getProject(orgId, projectId) {
|
|
1375
|
-
const response = await this.projectsApi.getProject(orgId, projectId);
|
|
1376
|
-
return response.data;
|
|
1377
|
-
}
|
|
1378
|
-
async listTasks() {
|
|
1379
|
-
const response = await this.tasksApi.listTasks(this.orgId, this.projectId);
|
|
1380
|
-
return response.data.results;
|
|
1381
|
-
}
|
|
1382
|
-
async getTask(taskId) {
|
|
1383
|
-
const response = await this.tasksApi.getTask(
|
|
1384
|
-
this.orgId,
|
|
1385
|
-
this.projectId,
|
|
1386
|
-
taskId,
|
|
1387
|
-
/* revisionTagId */
|
|
1388
|
-
null,
|
|
1389
|
-
/* includeTests */
|
|
1390
|
-
true
|
|
1391
|
-
);
|
|
1392
|
-
return response.data;
|
|
1393
|
-
}
|
|
1394
|
-
async runTask({
|
|
1395
|
-
body,
|
|
1396
|
-
taskId,
|
|
1397
|
-
revisionId,
|
|
1398
|
-
useFallbackModel = false
|
|
1399
|
-
}) {
|
|
1400
|
-
const form = new FormData();
|
|
1401
|
-
form.append("task_input", JSON.stringify(body.task_input));
|
|
1402
|
-
if (body.task_files && body.task_files.length > 0) {
|
|
1403
|
-
for (const fileInput of body.task_files) {
|
|
1404
|
-
form.append("task_file", fileInput, fileInput.name);
|
|
1405
|
-
}
|
|
1406
|
-
}
|
|
1407
|
-
const params = new URLSearchParams();
|
|
1408
|
-
if (revisionId) {
|
|
1409
|
-
params.set("revision_id", revisionId);
|
|
1410
|
-
}
|
|
1411
|
-
const response = await this.tasksApi.runTask(
|
|
1412
|
-
this.orgId,
|
|
1413
|
-
this.projectId,
|
|
1414
|
-
taskId,
|
|
1415
|
-
revisionId,
|
|
1416
|
-
/*revisionTag*/
|
|
1417
|
-
null,
|
|
1418
|
-
/*reportingGroup*/
|
|
1419
|
-
null,
|
|
1420
|
-
useFallbackModel,
|
|
1421
|
-
{
|
|
1422
|
-
data: form
|
|
1423
|
-
}
|
|
1424
|
-
);
|
|
1425
|
-
return response.data;
|
|
1426
|
-
}
|
|
1427
|
-
async listApiKeys() {
|
|
1428
|
-
const response = await this.apiKeysApi.listApiKeys(this.orgId, this.projectId);
|
|
1429
|
-
return response.data;
|
|
1430
|
-
}
|
|
1431
|
-
async createApiKey(name) {
|
|
1432
|
-
const response = await this.apiKeysApi.createApiKey(this.orgId, this.projectId, { name });
|
|
1433
|
-
return response.data;
|
|
1434
|
-
}
|
|
1435
|
-
};
|
|
1436
|
-
|
|
1437
|
-
// src/lib/resolve-path.ts
|
|
1438
|
-
import fs3 from "fs";
|
|
1439
|
-
import os2 from "os";
|
|
1440
|
-
import path3 from "path";
|
|
1441
|
-
function resolvePath(filePath) {
|
|
1442
|
-
if (filePath.startsWith("~/")) {
|
|
1443
|
-
return path3.join(os2.homedir(), filePath.slice(2));
|
|
1444
|
-
}
|
|
1445
|
-
return path3.resolve(filePath);
|
|
1446
|
-
}
|
|
1447
|
-
function resolvePathVariables(filePath, wd) {
|
|
1448
|
-
const srcDir = path3.join(wd, "src");
|
|
1449
|
-
const hasSrcDir = fs3.existsSync(srcDir);
|
|
1450
|
-
if (filePath.startsWith("$SRC/")) {
|
|
1451
|
-
const srcReplacement = hasSrcDir ? "src" : ".";
|
|
1452
|
-
const remainingPath = filePath.slice(5);
|
|
1453
|
-
return path3.join(srcReplacement, remainingPath);
|
|
1454
|
-
}
|
|
1455
|
-
if (filePath.startsWith("$SRC")) {
|
|
1456
|
-
const srcReplacement = hasSrcDir ? "src" : ".";
|
|
1457
|
-
const remainingPath = filePath.slice(4);
|
|
1458
|
-
return remainingPath ? path3.join(srcReplacement, remainingPath) : srcReplacement;
|
|
1459
|
-
}
|
|
1460
|
-
if (filePath.startsWith("$PWD/")) {
|
|
1461
|
-
const remainingPath = filePath.slice(5);
|
|
1462
|
-
return path3.join(".", remainingPath);
|
|
1463
|
-
}
|
|
1464
|
-
if (filePath.startsWith("$PWD")) {
|
|
1465
|
-
const remainingPath = filePath.slice(4);
|
|
1466
|
-
return remainingPath ? path3.join(".", remainingPath) : ".";
|
|
1467
|
-
}
|
|
1468
|
-
return filePath;
|
|
1469
|
-
}
|
|
1470
|
-
|
|
1471
|
-
// src/lib/wait-for-enter.ts
|
|
1472
|
-
var waitForEnter = (cb) => {
|
|
1473
|
-
let cleaned = false;
|
|
1474
|
-
const cleanup = () => {
|
|
1475
|
-
if (cleaned) {
|
|
1476
|
-
return;
|
|
1477
|
-
}
|
|
1478
|
-
cleaned = true;
|
|
1479
|
-
process.stdin.removeListener("data", listener);
|
|
1480
|
-
};
|
|
1481
|
-
const listener = (data) => {
|
|
1482
|
-
const key = data.toString();
|
|
1483
|
-
if (key.includes("\r") || key.includes("\n")) {
|
|
1484
|
-
cleanup();
|
|
1485
|
-
cb();
|
|
1486
|
-
}
|
|
1487
|
-
};
|
|
1488
|
-
process.stdin.on("data", listener);
|
|
1489
|
-
return cleanup;
|
|
1490
|
-
};
|
|
1491
|
-
|
|
1492
|
-
// src/commands/create-task/create-task-from-file.ts
|
|
1493
|
-
import * as p6 from "@clack/prompts";
|
|
1494
|
-
import fs4 from "fs";
|
|
1495
|
-
import path4 from "path";
|
|
1496
|
-
import { isMap, isScalar, parseDocument } from "yaml";
|
|
1497
|
-
function readYamlFile(file) {
|
|
1498
|
-
const fileContent = fs4.readFileSync(file, "utf8");
|
|
1499
|
-
const doc = parseDocument(fileContent);
|
|
1500
|
-
return doc.toJS();
|
|
1501
|
-
}
|
|
1502
|
-
function supportsTemperature(model) {
|
|
1503
|
-
return !!model.model_params?.temperature;
|
|
1504
|
-
}
|
|
1505
|
-
function updateYamlFileWithModel(file, data) {
|
|
1506
|
-
const fileContent = fs4.readFileSync(file, "utf8");
|
|
1507
|
-
const doc = parseDocument(fileContent);
|
|
1508
|
-
if (data.model) {
|
|
1509
|
-
doc.set("llm_model_id", data.model.id);
|
|
1510
|
-
const llmModelId = doc.get("llm_model_id", true);
|
|
1511
|
-
if (isScalar(llmModelId)) {
|
|
1512
|
-
llmModelId.commentBefore = " Selected model: " + data.model.name;
|
|
1513
|
-
}
|
|
1514
|
-
if (!supportsTemperature(data.model)) {
|
|
1515
|
-
const llmConfig = doc.get("llm_config", true);
|
|
1516
|
-
if (isMap(llmConfig) && llmConfig.has("temperature")) {
|
|
1517
|
-
const value = llmConfig.get("temperature");
|
|
1518
|
-
llmConfig.commentBefore = `Temperature config is not available to selected model
|
|
1519
|
-
temperature: ${value}`;
|
|
1520
|
-
llmConfig.delete("temperature");
|
|
1521
|
-
}
|
|
1522
|
-
} else {
|
|
1523
|
-
const llmConfig = doc.get("llm_config", true);
|
|
1524
|
-
const commentPrefix = "Temperature config is not available to selected model\ntemperature: ";
|
|
1525
|
-
if (isMap(llmConfig) && llmConfig.commentBefore?.startsWith(commentPrefix)) {
|
|
1526
|
-
const savedTemperature = llmConfig.commentBefore.slice(commentPrefix.length).trim();
|
|
1527
|
-
const parsedTemperature = parseFloat(savedTemperature);
|
|
1528
|
-
if (!isNaN(parsedTemperature)) {
|
|
1529
|
-
llmConfig.set("temperature", parsedTemperature);
|
|
1530
|
-
}
|
|
1531
|
-
llmConfig.commentBefore = void 0;
|
|
1532
|
-
}
|
|
1533
|
-
}
|
|
1534
|
-
}
|
|
1535
|
-
if (data.taskName) {
|
|
1536
|
-
doc.set("name", data.taskName);
|
|
1537
|
-
}
|
|
1538
|
-
const updatedContent = doc.toString();
|
|
1539
|
-
fs4.writeFileSync(file, updatedContent, "utf8");
|
|
1540
|
-
}
|
|
1541
|
-
async function getDefaultModel() {
|
|
1542
|
-
const [client, clientError] = await tryCatch(() => ApiClient.fromAuthContext());
|
|
1543
|
-
if (clientError || !client) {
|
|
1544
|
-
return null;
|
|
1545
|
-
}
|
|
1546
|
-
const [models, modelsError] = await tryCatch(() => client.listModels());
|
|
1547
|
-
if (modelsError || !models) {
|
|
1548
|
-
return null;
|
|
1549
|
-
}
|
|
1550
|
-
const modelList = models;
|
|
1551
|
-
const availableModels = modelList.filter((m) => !m.replaced_by);
|
|
1552
|
-
if (availableModels.length === 0) {
|
|
1553
|
-
return null;
|
|
1554
|
-
}
|
|
1555
|
-
const openaiModel = availableModels.find((m) => m.provider.toLowerCase() === "openai");
|
|
1556
|
-
return openaiModel || availableModels[0];
|
|
1557
|
-
}
|
|
1558
|
-
async function writeYamlFile(file) {
|
|
1559
|
-
const dir = path4.dirname(file);
|
|
1560
|
-
const [, mkdirError] = tryCatch(() => {
|
|
1561
|
-
if (dir && dir !== "." && !fs4.existsSync(dir)) {
|
|
1562
|
-
fs4.mkdirSync(dir, { recursive: true });
|
|
1563
|
-
}
|
|
1564
|
-
});
|
|
1565
|
-
if (mkdirError) {
|
|
1566
|
-
const message = mkdirError instanceof Error ? mkdirError.message : String(mkdirError);
|
|
1567
|
-
(isPiped ? console.error : p6.outro)(`Failed to create directory "${dir}": ${message}`);
|
|
1568
|
-
process.exit(1);
|
|
1569
|
-
}
|
|
1570
|
-
const url = `${buildEnv.CLI_PUBLIC_TEMPLATES_URL}/create-task.yaml`;
|
|
1571
|
-
const response = await fetch(url, {
|
|
1572
|
-
headers: {
|
|
1573
|
-
accept: "text/yaml"
|
|
1574
|
-
},
|
|
1575
|
-
redirect: "manual"
|
|
1576
|
-
});
|
|
1577
|
-
if (!response.ok) {
|
|
1578
|
-
;
|
|
1579
|
-
(isPiped ? console.error : p6.outro)(
|
|
1580
|
-
`Failed to download template: Server returned ${response.status} ${response.statusText}`
|
|
1581
|
-
);
|
|
1582
|
-
process.exit(1);
|
|
1583
|
-
}
|
|
1584
|
-
let fileContent = await response.text();
|
|
1585
|
-
const model = await getDefaultModel();
|
|
1586
|
-
if (model) {
|
|
1587
|
-
const doc = parseDocument(fileContent);
|
|
1588
|
-
doc.set("llm_model_id", model.id);
|
|
1589
|
-
const llmModelId = doc.get("llm_model_id", true);
|
|
1590
|
-
if (isScalar(llmModelId)) {
|
|
1591
|
-
llmModelId.commentBefore = " Selected model: " + model.name;
|
|
1592
|
-
}
|
|
1593
|
-
if (!supportsTemperature(model)) {
|
|
1594
|
-
const llmConfig = doc.get("llm_config", true);
|
|
1595
|
-
if (isMap(llmConfig) && llmConfig.has("temperature")) {
|
|
1596
|
-
const value = llmConfig.get("temperature");
|
|
1597
|
-
llmConfig.commentBefore = `Temperature config is not available to selected model
|
|
1598
|
-
temperature: ${value}`;
|
|
1599
|
-
llmConfig.delete("temperature");
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
1602
|
-
fileContent = doc.toString();
|
|
1603
|
-
}
|
|
1604
|
-
const [, writeError] = tryCatch(() => fs4.writeFileSync(file, fileContent, "utf8"));
|
|
1605
|
-
if (writeError) {
|
|
1606
|
-
const message = writeError instanceof Error ? writeError.message : String(writeError);
|
|
1607
|
-
(isPiped ? console.error : p6.outro)(`Failed to write file "${file}": ${message}`);
|
|
1608
|
-
process.exit(1);
|
|
1609
|
-
}
|
|
1610
|
-
}
|
|
1611
|
-
|
|
1612
|
-
// src/commands/create-task/index.ts
|
|
1613
|
-
import * as p7 from "@clack/prompts";
|
|
1614
|
-
import { isAxiosError as isAxiosError2 } from "axios";
|
|
1615
|
-
import fs5 from "fs";
|
|
1616
|
-
import open from "open";
|
|
1617
|
-
import pc4 from "picocolors";
|
|
1618
|
-
function registerCreateTaskCommand(program) {
|
|
1619
|
-
program.description("Create a new task").option("-f, --file <file>", "Create task from a YAML file.").action(runCreateTask);
|
|
1620
|
-
}
|
|
1621
|
-
async function runCreateTask(options) {
|
|
1622
|
-
spinner3.start();
|
|
1623
|
-
await ApiClient.fromAuthContext();
|
|
1624
|
-
spinner3.stop();
|
|
1625
|
-
if (!options.file) {
|
|
1626
|
-
const throughDashboard = await p7.confirm({
|
|
1627
|
-
message: "Create tasks directly from the dashboard",
|
|
1628
|
-
initialValue: true
|
|
1629
|
-
});
|
|
1630
|
-
if (p7.isCancel(throughDashboard)) {
|
|
1631
|
-
p7.outro("Create task cancelled");
|
|
1632
|
-
process.exit(0);
|
|
1633
|
-
} else if (!throughDashboard) {
|
|
1634
|
-
await handleTemplateWorkflow(options);
|
|
1635
|
-
} else {
|
|
1636
|
-
await handleDashboardWorkflow();
|
|
1637
|
-
}
|
|
1638
|
-
return;
|
|
1639
|
-
}
|
|
1640
|
-
await handleTemplateWorkflow(options);
|
|
1641
|
-
}
|
|
1642
|
-
var spinner3 = getSpinner();
|
|
1643
|
-
async function handleDashboardWorkflow() {
|
|
1644
|
-
const client = await ApiClient.fromAuthContext();
|
|
1645
|
-
spinner3.start();
|
|
1646
|
-
const [project] = await tryCatch(() => client.getProject(client.orgId, client.projectId));
|
|
1647
|
-
spinner3.stop();
|
|
1648
|
-
const projectName = project?.name || client.projectId;
|
|
1649
|
-
p7.log.info(`Opening dashboard to create task in project: ${pc4.cyan(projectName)}`);
|
|
1650
|
-
const clackSpinner = p7.spinner();
|
|
1651
|
-
clackSpinner.start("Press Enter to open dashboard...");
|
|
1652
|
-
waitForEnter(async () => {
|
|
1653
|
-
const createTaskUrl = `${buildEnv.CLI_PUBLIC_TEMPLATES_URL}/server/org/${client.orgId}/project/${client.projectId}/task/create`;
|
|
1654
|
-
const [, openError] = await tryCatch(() => open(createTaskUrl));
|
|
1655
|
-
if (openError) {
|
|
1656
|
-
clackSpinner.stop(`Could not open browser automatically.`);
|
|
1657
|
-
p7.outro(`Please open the URL manually: ${pc4.cyan(createTaskUrl)}`);
|
|
1658
|
-
process.exit(1);
|
|
1659
|
-
}
|
|
1660
|
-
clackSpinner.stop("Dashboard opened in your browser");
|
|
1661
|
-
p7.outro(pc4.green(`Please create the task in the dashboard.`));
|
|
1662
|
-
process.exit(0);
|
|
1663
|
-
});
|
|
1664
|
-
}
|
|
1665
|
-
var defaultTemplatePath = "create-task.yaml";
|
|
1666
|
-
async function handleTemplateWorkflow({ file }) {
|
|
1667
|
-
if (file) {
|
|
1668
|
-
const filePath2 = resolvePath(file);
|
|
1669
|
-
let task = null;
|
|
1670
|
-
let creatingTaskError = null;
|
|
1671
|
-
let client = null;
|
|
1672
|
-
while (true) {
|
|
1673
|
-
spinner3.start();
|
|
1674
|
-
client = await ApiClient.fromAuthContext();
|
|
1675
|
-
const currentClient = client;
|
|
1676
|
-
const [taskData, taskError] = tryCatch(() => readYamlFile(filePath2));
|
|
1677
|
-
const [taskResult, error] = await tryCatch(() => currentClient.createTask(taskData));
|
|
1678
|
-
spinner3.stop();
|
|
1679
|
-
if (taskError) {
|
|
1680
|
-
handleApiError(taskError);
|
|
1681
|
-
process.exit(1);
|
|
1682
|
-
}
|
|
1683
|
-
if (error) {
|
|
1684
|
-
if (isPiped || !isAxiosError2(error)) {
|
|
1685
|
-
handleApiError(error);
|
|
1686
|
-
process.exit(1);
|
|
1687
|
-
}
|
|
1688
|
-
const response = error.response?.data;
|
|
1689
|
-
if (typeof response === "object" && response !== null && "detail" in response && Array.isArray(response.detail)) {
|
|
1690
|
-
const detail = response.detail;
|
|
1691
|
-
if (Array.isArray(detail) && detail.length > 0) {
|
|
1692
|
-
if (detail.find((e) => typeof e === "object" && e !== null && "type" in e && e.type === "invalid_llm_model")) {
|
|
1693
|
-
p7.log.error("Invalid LLM model ID detected.");
|
|
1694
|
-
spinner3.start();
|
|
1695
|
-
const [models, modelsError] = await tryCatch(() => currentClient.listModels());
|
|
1696
|
-
spinner3.stop();
|
|
1697
|
-
if (modelsError || !models) {
|
|
1698
|
-
p7.cancel(
|
|
1699
|
-
`Error: Failed to fetch available models. Please run ${pc4.cyan(`npx rightbrain list-models`)} to see available models.`
|
|
1700
|
-
);
|
|
1701
|
-
process.exit(1);
|
|
1702
|
-
}
|
|
1703
|
-
const modelList = models;
|
|
1704
|
-
const availableModels = modelList.filter((m) => !m.replaced_by);
|
|
1705
|
-
if (availableModels.length === 0) {
|
|
1706
|
-
p7.cancel(`Error: No available models found.`);
|
|
1707
|
-
process.exit(1);
|
|
1708
|
-
}
|
|
1709
|
-
const grouped = groupModelsByProvider(availableModels);
|
|
1710
|
-
const sortedProviders = Array.from(grouped.keys()).sort();
|
|
1711
|
-
const options = [];
|
|
1712
|
-
for (const provider of sortedProviders) {
|
|
1713
|
-
const providerModels = grouped.get(provider);
|
|
1714
|
-
for (const model of providerModels) {
|
|
1715
|
-
options.push({
|
|
1716
|
-
value: model.id,
|
|
1717
|
-
label: `${model.alias} (${provider})`,
|
|
1718
|
-
hint: model.id
|
|
1719
|
-
});
|
|
1720
|
-
}
|
|
1721
|
-
}
|
|
1722
|
-
const selectedModelId = await p7.autocomplete({
|
|
1723
|
-
message: "Please select a valid LLM model:",
|
|
1724
|
-
options
|
|
1725
|
-
});
|
|
1726
|
-
if (p7.isCancel(selectedModelId)) {
|
|
1727
|
-
p7.outro("Model selection cancelled");
|
|
1728
|
-
process.exit(0);
|
|
1729
|
-
}
|
|
1730
|
-
const selectedModel = availableModels.find((m) => m.id === selectedModelId);
|
|
1731
|
-
if (!selectedModel) {
|
|
1732
|
-
p7.cancel(`Error: Selected model not found.`);
|
|
1733
|
-
process.exit(1);
|
|
1734
|
-
}
|
|
1735
|
-
try {
|
|
1736
|
-
updateYamlFileWithModel(filePath2, { model: selectedModel });
|
|
1737
|
-
p7.log.info(`Updated ${pc4.cyan(file)} with model: ${pc4.cyan(selectedModel.alias)}`);
|
|
1738
|
-
} catch (updateError) {
|
|
1739
|
-
p7.cancel(
|
|
1740
|
-
`Error: Failed to update file: ${updateError instanceof Error ? updateError.message : String(updateError)}`
|
|
1741
|
-
);
|
|
1742
|
-
process.exit(1);
|
|
1743
|
-
}
|
|
1744
|
-
continue;
|
|
1745
|
-
}
|
|
1746
|
-
if (detail.find((e) => typeof e === "object" && e !== null && "type" in e && e.type === "duplicate_task_name")) {
|
|
1747
|
-
p7.log.error("Duplicate task name detected.");
|
|
1748
|
-
const newTaskName = await p7.text({
|
|
1749
|
-
message: "Please enter a unique task name:",
|
|
1750
|
-
placeholder: "Enter task name",
|
|
1751
|
-
initialValue: taskData && typeof taskData === "object" && "name" in taskData && typeof taskData.name === "string" ? taskData?.name || "" : "",
|
|
1752
|
-
validate(value) {
|
|
1753
|
-
if (typeof value === "string" && value.trim() === ".") {
|
|
1754
|
-
return "File path cannot be empty";
|
|
1755
|
-
}
|
|
1756
|
-
if (typeof value === "string" && value.trim() === taskData?.name) {
|
|
1757
|
-
return "Task name cannot be the same as the existing task";
|
|
1758
|
-
}
|
|
1759
|
-
}
|
|
1760
|
-
});
|
|
1761
|
-
if (p7.isCancel(newTaskName)) {
|
|
1762
|
-
p7.outro("Create task cancelled");
|
|
1763
|
-
process.exit(0);
|
|
1764
|
-
}
|
|
1765
|
-
try {
|
|
1766
|
-
updateYamlFileWithModel(filePath2, { taskName: newTaskName });
|
|
1767
|
-
p7.log.info(`Updated ${pc4.cyan(file)} with task name: ${pc4.cyan(newTaskName)}`);
|
|
1768
|
-
} catch (updateError) {
|
|
1769
|
-
p7.outro(
|
|
1770
|
-
`Error: Failed to update file: ${updateError instanceof Error ? updateError.message : String(updateError)}`
|
|
1771
|
-
);
|
|
1772
|
-
process.exit(1);
|
|
1773
|
-
}
|
|
1774
|
-
continue;
|
|
1775
|
-
}
|
|
1776
|
-
}
|
|
1777
|
-
}
|
|
1778
|
-
}
|
|
1779
|
-
task = taskResult;
|
|
1780
|
-
creatingTaskError = error;
|
|
1781
|
-
break;
|
|
1782
|
-
}
|
|
1783
|
-
if (!task) {
|
|
1784
|
-
if (creatingTaskError) {
|
|
1785
|
-
handleApiError(creatingTaskError);
|
|
1786
|
-
} else {
|
|
1787
|
-
;
|
|
1788
|
-
(isPiped ? console.error : p7.outro)(pc4.red("Failed to create task"));
|
|
1789
|
-
}
|
|
1790
|
-
process.exit(1);
|
|
1791
|
-
}
|
|
1792
|
-
const taskId = task.id;
|
|
1793
|
-
const dashboardUrl = `${buildEnv.CLI_PUBLIC_TEMPLATES_URL}/server/org/${client.orgId}/project/${client.projectId}/task/${taskId}`;
|
|
1794
|
-
if (isPiped) {
|
|
1795
|
-
console.info(JSON.stringify(task, null, 2));
|
|
1796
|
-
process.exit(0);
|
|
1797
|
-
}
|
|
1798
|
-
p7.log.info(`Run this task with ${pc4.cyan(`npx rightbrain run-task --task ${taskId}`)} command.`);
|
|
1799
|
-
p7.log.info(`View in dashboard:
|
|
1800
|
-
${pc4.cyan(dashboardUrl)}`);
|
|
1801
|
-
p7.outro(pc4.green(`Task created successfully!`));
|
|
1802
|
-
process.exit(0);
|
|
1803
|
-
}
|
|
1804
|
-
const filePath = await p7.text({
|
|
1805
|
-
message: "Where would you like to save the task file?",
|
|
1806
|
-
placeholder: defaultTemplatePath,
|
|
1807
|
-
defaultValue: defaultTemplatePath,
|
|
1808
|
-
validate(value) {
|
|
1809
|
-
if (typeof value === "string" && value.trim() === ".") {
|
|
1810
|
-
return "File path cannot be empty";
|
|
1811
|
-
}
|
|
1812
|
-
}
|
|
1813
|
-
});
|
|
1814
|
-
if (p7.isCancel(filePath)) {
|
|
1815
|
-
p7.outro("Create task cancelled");
|
|
1816
|
-
process.exit(0);
|
|
1817
|
-
}
|
|
1818
|
-
const taskPath = filePath || defaultTemplatePath;
|
|
1819
|
-
const targetPath = resolvePath(taskPath);
|
|
1820
|
-
if (fs5.existsSync(targetPath)) {
|
|
1821
|
-
const overwrite = await p7.confirm({
|
|
1822
|
-
message: `File "${taskPath}" already exists. Overwrite?`,
|
|
1823
|
-
initialValue: false
|
|
1824
|
-
});
|
|
1825
|
-
if (p7.isCancel(overwrite) || !overwrite) {
|
|
1826
|
-
p7.outro("Create task cancelled");
|
|
1827
|
-
process.exit(0);
|
|
1828
|
-
}
|
|
1829
|
-
}
|
|
1830
|
-
spinner3.start();
|
|
1831
|
-
await writeYamlFile(targetPath);
|
|
1832
|
-
spinner3.stop();
|
|
1833
|
-
p7.outro(`Edit the file, then run ${pc4.cyan(`npx rightbrain create-task --file ${taskPath}`)} to create the task.`);
|
|
1834
|
-
process.exit(0);
|
|
1835
|
-
}
|
|
1836
|
-
|
|
1837
|
-
// src/lib/fetch-json.ts
|
|
1838
|
-
import * as p8 from "@clack/prompts";
|
|
1839
|
-
import pc5 from "picocolors";
|
|
1840
|
-
function getErrorMessage2(error) {
|
|
1841
|
-
return error instanceof Error ? error.message : String(error);
|
|
1842
|
-
}
|
|
1843
|
-
var spinner4 = getSpinner();
|
|
1844
|
-
async function fetchJson(url, errorContext) {
|
|
1845
|
-
spinner4.start();
|
|
1846
|
-
const [response, fetchError] = await tryCatch(async () => {
|
|
1847
|
-
const res = await fetch(url);
|
|
1848
|
-
if (!res.ok) {
|
|
1849
|
-
throw new Error(`Failed to fetch ${errorContext}: ${res.status} ${res.statusText}`);
|
|
1850
|
-
}
|
|
1851
|
-
return res;
|
|
1852
|
-
});
|
|
1853
|
-
spinner4.stop();
|
|
1854
|
-
if (fetchError || !response) {
|
|
1855
|
-
const errorMessage = getErrorMessage2(fetchError);
|
|
1856
|
-
p8.log.error(pc5.red(`Failed to fetch ${errorContext}: ${errorMessage}`));
|
|
1857
|
-
throw new Error(`Failed to fetch ${errorContext}: ${errorMessage}`);
|
|
1858
|
-
}
|
|
1859
|
-
const [data, parseError] = await tryCatch(async () => {
|
|
1860
|
-
const text5 = await response.text();
|
|
1861
|
-
return JSON.parse(text5);
|
|
1862
|
-
});
|
|
1863
|
-
if (parseError || !data) {
|
|
1864
|
-
const errorMessage = getErrorMessage2(parseError);
|
|
1865
|
-
p8.log.error(pc5.red(`Failed to parse ${errorContext}: ${errorMessage}`));
|
|
1866
|
-
throw new Error(`Failed to parse ${errorContext}: ${errorMessage}`);
|
|
1867
|
-
}
|
|
1868
|
-
return data;
|
|
1869
|
-
}
|
|
1870
|
-
|
|
1871
|
-
// src/lib/discovery.ts
|
|
1872
|
-
var discoveryCache = null;
|
|
1873
|
-
async function fetchDiscovery(baseUrl) {
|
|
1874
|
-
if (discoveryCache) {
|
|
1875
|
-
return discoveryCache;
|
|
1876
|
-
}
|
|
1877
|
-
const data = await fetchJson(`${baseUrl}/discovery.json`, "discovery configuration");
|
|
1878
|
-
discoveryCache = data;
|
|
1879
|
-
return data;
|
|
1880
|
-
}
|
|
1881
|
-
|
|
1882
|
-
// src/lib/task-utils.ts
|
|
1883
|
-
function revisionsWithVersion(data) {
|
|
1884
|
-
const oldestToNewest = data.map((item) => ({ ...item, version: "0" })).sort((a, b) => a.created.localeCompare(b.created));
|
|
1885
|
-
let majorVersionCounter = 1;
|
|
1886
|
-
let minorVersionCounter = 1;
|
|
1887
|
-
for (let i = 0; i < oldestToNewest.length; i++) {
|
|
1888
|
-
if (!oldestToNewest[i].test) {
|
|
1889
|
-
oldestToNewest[i].version = `${majorVersionCounter}`;
|
|
1890
|
-
majorVersionCounter++;
|
|
1891
|
-
minorVersionCounter = 1;
|
|
1892
|
-
} else {
|
|
1893
|
-
const baseMajorVersion = majorVersionCounter === 1 ? 1 : majorVersionCounter - 1;
|
|
1894
|
-
oldestToNewest[i].version = `${baseMajorVersion}.${minorVersionCounter}`;
|
|
1895
|
-
minorVersionCounter++;
|
|
1896
|
-
}
|
|
1897
|
-
}
|
|
1898
|
-
return oldestToNewest.reverse();
|
|
1899
|
-
}
|
|
1900
|
-
var getActiveRevision = (task) => {
|
|
1901
|
-
if (!task) return;
|
|
1902
|
-
if (task.active_revisions.length === 0) return;
|
|
1903
|
-
let maxWeight = task.active_revisions[0].weight ?? 0;
|
|
1904
|
-
let maxWeightIndex = 0;
|
|
1905
|
-
for (let i = 1; i < task.active_revisions.length; i++) {
|
|
1906
|
-
const weight = task.active_revisions?.[i]?.weight;
|
|
1907
|
-
if (typeof weight === "number" && weight > maxWeight) {
|
|
1908
|
-
maxWeight = weight;
|
|
1909
|
-
maxWeightIndex = i;
|
|
1910
|
-
}
|
|
1911
|
-
}
|
|
1912
|
-
const revision = task?.revisions?.find((e) => e.id === task.active_revisions[maxWeightIndex].task_revision_id);
|
|
1913
|
-
return revision;
|
|
1914
|
-
};
|
|
1915
|
-
|
|
1916
|
-
// src/commands/generate/template-loader.ts
|
|
1917
|
-
import { Eta } from "eta";
|
|
1918
|
-
var templateCache = null;
|
|
1919
|
-
async function fetchTemplate(name) {
|
|
1920
|
-
if (templateCache && templateCache[name]) {
|
|
1921
|
-
return templateCache[name];
|
|
1922
|
-
}
|
|
1923
|
-
const url = `${buildEnv.CLI_PUBLIC_TEMPLATES_URL}/${name}.eta`;
|
|
1924
|
-
const response = await fetch(url);
|
|
1925
|
-
if (!response.ok) {
|
|
1926
|
-
throw new Error(`Failed to fetch template ${name}: ${response.status} ${response.statusText}`);
|
|
1927
|
-
}
|
|
1928
|
-
const content = await response.text();
|
|
1929
|
-
if (!templateCache) {
|
|
1930
|
-
templateCache = {};
|
|
1931
|
-
}
|
|
1932
|
-
templateCache[name] = content;
|
|
1933
|
-
return content;
|
|
1934
|
-
}
|
|
1935
|
-
async function createEtaInstance(language) {
|
|
1936
|
-
const eta = new Eta({
|
|
1937
|
-
autoEscape: false,
|
|
1938
|
-
autoTrim: false,
|
|
1939
|
-
cache: true
|
|
1940
|
-
});
|
|
1941
|
-
const taskTypesTemplate = await fetchTemplate(language);
|
|
1942
|
-
eta.loadTemplate("@task-types", taskTypesTemplate);
|
|
1943
|
-
return eta;
|
|
1944
|
-
}
|
|
1945
|
-
var TEMPLATES = {
|
|
1946
|
-
TASK_TYPES: "@task-types"
|
|
1947
|
-
};
|
|
1948
|
-
|
|
1949
|
-
// src/commands/generate/type-generator.ts
|
|
1950
|
-
import camelCase from "just-camel-case";
|
|
1951
|
-
import kebabCase from "just-kebab-case";
|
|
1952
|
-
import pascalCase from "just-pascal-case";
|
|
1953
|
-
import snakeCase from "just-snake-case";
|
|
1954
|
-
function generateFileName(taskName, filenameCase = "kebab-case") {
|
|
1955
|
-
switch (filenameCase) {
|
|
1956
|
-
case "camel-case":
|
|
1957
|
-
return `${camelCase(taskName)}.ts`;
|
|
1958
|
-
case "kebab-case":
|
|
1959
|
-
return `${kebabCase(taskName)}.ts`;
|
|
1960
|
-
case "pascal-case":
|
|
1961
|
-
return `${pascalCase(taskName)}.ts`;
|
|
1962
|
-
case "snake-case":
|
|
1963
|
-
return `${snakeCase(taskName)}.ts`;
|
|
1964
|
-
}
|
|
1965
|
-
return `${kebabCase(taskName)}.ts`;
|
|
1966
|
-
}
|
|
1967
|
-
function extractEnvVarName(value) {
|
|
1968
|
-
const match = value.match(/^\$\{([^}]+)\}$/);
|
|
1969
|
-
return match ? match[1].trim() : null;
|
|
1970
|
-
}
|
|
1971
|
-
function buildTaskIdEnvMap(rawConfig) {
|
|
1972
|
-
const envMap = {};
|
|
1973
|
-
if (rawConfig && typeof rawConfig === "object" && "generate" in rawConfig && rawConfig.generate && typeof rawConfig.generate === "object" && "taskIds" in rawConfig.generate && Array.isArray(rawConfig.generate.taskIds)) {
|
|
1974
|
-
for (const rawTaskId of rawConfig.generate.taskIds) {
|
|
1975
|
-
if (typeof rawTaskId === "string") {
|
|
1976
|
-
const envVarName = extractEnvVarName(rawTaskId);
|
|
1977
|
-
if (envVarName) {
|
|
1978
|
-
const resolvedTaskId = process.env[envVarName];
|
|
1979
|
-
if (resolvedTaskId) {
|
|
1980
|
-
envMap[resolvedTaskId] = envVarName;
|
|
1981
|
-
}
|
|
1982
|
-
}
|
|
1983
|
-
}
|
|
1984
|
-
}
|
|
1985
|
-
}
|
|
1986
|
-
return envMap;
|
|
1987
|
-
}
|
|
1988
|
-
function isHardcodeTaskIdsEnabled(rawConfig) {
|
|
1989
|
-
if (rawConfig && typeof rawConfig === "object" && "generate" in rawConfig && rawConfig.generate && typeof rawConfig.generate === "object" && "hardcodeTaskIds" in rawConfig.generate) {
|
|
1990
|
-
return rawConfig.generate.hardcodeTaskIds === true;
|
|
1991
|
-
}
|
|
1992
|
-
return false;
|
|
1993
|
-
}
|
|
1994
|
-
function buildTemplateContext(_task, rawConfig) {
|
|
1995
|
-
const revisions = revisionsWithVersion(_task.revisions || []);
|
|
1996
|
-
const task = { ..._task, revisions };
|
|
1997
|
-
const activeRevision = getActiveRevision({ ...task, revisions });
|
|
1998
|
-
if (!activeRevision) {
|
|
1999
|
-
return null;
|
|
2000
|
-
}
|
|
2001
|
-
const hardcodeTaskIds = rawConfig ? isHardcodeTaskIdsEnabled(rawConfig) : false;
|
|
2002
|
-
const taskIdEnvMap = rawConfig && !hardcodeTaskIds ? buildTaskIdEnvMap(rawConfig) : {};
|
|
2003
|
-
const taskIdEnvVar = taskIdEnvMap[task.id] || null;
|
|
2004
|
-
return { task, activeRevision, taskIdEnvVar, utils: { camelCase, kebabCase, pascalCase, snakeCase } };
|
|
2005
|
-
}
|
|
2006
|
-
var TypeGenerator = class {
|
|
2007
|
-
constructor(language, languageConfig) {
|
|
2008
|
-
this.language = language;
|
|
2009
|
-
this.languageConfig = languageConfig;
|
|
2010
|
-
}
|
|
2011
|
-
eta = null;
|
|
2012
|
-
initialized = false;
|
|
2013
|
-
async initialize() {
|
|
2014
|
-
if (this.initialized) {
|
|
2015
|
-
return;
|
|
2016
|
-
}
|
|
2017
|
-
this.eta = await createEtaInstance(this.language);
|
|
2018
|
-
this.initialized = true;
|
|
2019
|
-
}
|
|
2020
|
-
generate(task, rawConfig) {
|
|
2021
|
-
if (!this.eta) {
|
|
2022
|
-
throw new Error("TypeGenerator not initialized. Call initialize() first.");
|
|
2023
|
-
}
|
|
2024
|
-
const context = buildTemplateContext(task, rawConfig);
|
|
2025
|
-
if (!context) {
|
|
2026
|
-
return null;
|
|
2027
|
-
}
|
|
2028
|
-
const content = this.eta.render(TEMPLATES.TASK_TYPES, context);
|
|
2029
|
-
const fileName = generateFileName(task.name, this.languageConfig.filenameCase);
|
|
2030
|
-
return { fileName, content };
|
|
2031
|
-
}
|
|
2032
|
-
generateAll(tasks, rawConfig) {
|
|
2033
|
-
const results = [];
|
|
2034
|
-
for (const task of tasks) {
|
|
2035
|
-
const result = this.generate(task, rawConfig);
|
|
2036
|
-
if (result) {
|
|
2037
|
-
results.push(result);
|
|
2038
|
-
}
|
|
2039
|
-
}
|
|
2040
|
-
return results;
|
|
2041
|
-
}
|
|
2042
|
-
};
|
|
2043
|
-
|
|
2044
|
-
// src/commands/generate/generate-command.ts
|
|
2045
|
-
import * as p9 from "@clack/prompts";
|
|
2046
|
-
import fs6 from "fs";
|
|
2047
|
-
import path5 from "path";
|
|
2048
|
-
import pc6 from "picocolors";
|
|
2049
|
-
function getErrorMessage3(error) {
|
|
2050
|
-
if (error instanceof Error) {
|
|
2051
|
-
return error.message;
|
|
2052
|
-
}
|
|
2053
|
-
return String(error);
|
|
2054
|
-
}
|
|
2055
|
-
async function runGenerateCommand(props) {
|
|
2056
|
-
const { init = false } = props || {};
|
|
2057
|
-
const config2 = loadConfigOrExit();
|
|
2058
|
-
const outputDir = config2.generate?.outputDir;
|
|
2059
|
-
if (!outputDir) {
|
|
2060
|
-
p9.cancel(`Error: No generate.outputDir found in ${config2.filePath}`);
|
|
2061
|
-
process.exit(1);
|
|
2062
|
-
}
|
|
2063
|
-
const taskIds = config2.generate?.taskIds || [];
|
|
2064
|
-
if (taskIds.length === 0) {
|
|
2065
|
-
p9.cancel(`Error: No generate.taskIds found in ${config2.filePath}`);
|
|
2066
|
-
process.exit(1);
|
|
2067
|
-
}
|
|
2068
|
-
const language = config2.generate?.language;
|
|
2069
|
-
if (!language) {
|
|
2070
|
-
p9.cancel(`Error: No generate.language found in ${config2.filePath}`);
|
|
2071
|
-
process.exit(1);
|
|
2072
|
-
}
|
|
2073
|
-
const configDir = path5.dirname(config2.filePath);
|
|
2074
|
-
const outputPath = path5.resolve(configDir, outputDir);
|
|
2075
|
-
const [discovery, fetchDiscoveryError] = await tryCatch(() => fetchDiscovery(buildEnv.CLI_PUBLIC_TEMPLATES_URL));
|
|
2076
|
-
if (fetchDiscoveryError || !discovery) {
|
|
2077
|
-
p9.cancel(`Failed to fetch discovery: ${getErrorMessage3(fetchDiscoveryError)}`);
|
|
2078
|
-
process.exit(1);
|
|
2079
|
-
}
|
|
2080
|
-
const languageConfig = discovery[language];
|
|
2081
|
-
if (!languageConfig) {
|
|
2082
|
-
p9.cancel(`Language ${language} is not currently supported.`);
|
|
2083
|
-
process.exit(1);
|
|
2084
|
-
}
|
|
2085
|
-
const clackSpinner = p9.spinner();
|
|
2086
|
-
clackSpinner.start("Generating types");
|
|
2087
|
-
const generator = new TypeGenerator(language, languageConfig);
|
|
2088
|
-
await generator.initialize();
|
|
2089
|
-
const client = await ApiClient.fromConfig();
|
|
2090
|
-
const taskIdSet = new Set(taskIds);
|
|
2091
|
-
const fullTasks = [];
|
|
2092
|
-
for (const taskId of taskIdSet) {
|
|
2093
|
-
const [fullTask, fetchError] = await tryCatch(() => client.getTask(taskId));
|
|
2094
|
-
if (fetchError) {
|
|
2095
|
-
clackSpinner.stop("Something went wrong");
|
|
2096
|
-
p9.log.warn(pc6.yellow(`Failed to fetch task ${taskId}: ${getErrorMessage3(fetchError)}`));
|
|
2097
|
-
clackSpinner.start("Generating types");
|
|
2098
|
-
continue;
|
|
2099
|
-
}
|
|
2100
|
-
if (fullTask) {
|
|
2101
|
-
fullTasks.push(fullTask);
|
|
2102
|
-
}
|
|
2103
|
-
}
|
|
2104
|
-
if (fullTasks.length === 0 && !init) {
|
|
2105
|
-
p9.cancel("No valid tasks to generate.");
|
|
2106
|
-
process.exit(0);
|
|
2107
|
-
}
|
|
2108
|
-
const [results, generateError] = tryCatch(() => generator.generateAll(fullTasks, config2.rawConfig));
|
|
2109
|
-
if (!results) {
|
|
2110
|
-
clackSpinner.stop("Something went wrong");
|
|
2111
|
-
p9.cancel(`Failed to generate types: ${getErrorMessage3(generateError)}`);
|
|
2112
|
-
process.exit(1);
|
|
2113
|
-
}
|
|
2114
|
-
const [, mkdirError] = await tryCatch(() => fs6.promises.mkdir(outputPath, { recursive: true }));
|
|
2115
|
-
if (mkdirError) {
|
|
2116
|
-
clackSpinner.stop("Something went wrong");
|
|
2117
|
-
p9.cancel(`Failed to create output directory: ${getErrorMessage3(mkdirError)}`);
|
|
2118
|
-
process.exit(1);
|
|
2119
|
-
}
|
|
2120
|
-
const writtenFiles = [];
|
|
2121
|
-
for (const { fileName, content } of results) {
|
|
2122
|
-
const filePath = path5.join(outputPath, fileName);
|
|
2123
|
-
const [, writeError] = await tryCatch(() => fs6.promises.writeFile(filePath, content, "utf-8"));
|
|
2124
|
-
if (writeError) {
|
|
2125
|
-
p9.log.warn(pc6.yellow(`Failed to write ${fileName}: ${getErrorMessage3(writeError)}`));
|
|
2126
|
-
continue;
|
|
2127
|
-
}
|
|
2128
|
-
writtenFiles.push(fileName);
|
|
2129
|
-
}
|
|
2130
|
-
clackSpinner.stop(`Generated ${pc6.cyan(writtenFiles.length.toString())} file(s) at ${pc6.green(outputPath)}`);
|
|
2131
|
-
if (!init) {
|
|
2132
|
-
p9.outro(pc6.green("Done!"));
|
|
2133
|
-
}
|
|
2134
|
-
}
|
|
2135
|
-
|
|
2136
|
-
// src/commands/generate/index.ts
|
|
2137
|
-
function registerGenerateCommand(program) {
|
|
2138
|
-
program.description("Generate task interface for your RightBrain tasks").action(runGenerateCommand);
|
|
2139
|
-
}
|
|
2140
|
-
|
|
2141
|
-
// src/commands/init/template-discovery.ts
|
|
2142
|
-
import * as p10 from "@clack/prompts";
|
|
2143
|
-
import fs7 from "fs";
|
|
2144
|
-
import path6 from "path";
|
|
2145
|
-
import pc7 from "picocolors";
|
|
2146
|
-
var spinner6 = getSpinner();
|
|
2147
|
-
function isFile(filePath) {
|
|
2148
|
-
return fs7.existsSync(filePath) && fs7.statSync(filePath).isFile();
|
|
2149
|
-
}
|
|
2150
|
-
function isDirectory(dirPath) {
|
|
2151
|
-
return fs7.existsSync(dirPath) && fs7.statSync(dirPath).isDirectory();
|
|
2152
|
-
}
|
|
2153
|
-
function evaluateRequirement(expr, wd, matchedEnvs) {
|
|
2154
|
-
if ("all" in expr) {
|
|
2155
|
-
return expr.all.every((subExpr) => evaluateRequirement(subExpr, wd, matchedEnvs));
|
|
2156
|
-
}
|
|
2157
|
-
if ("any" in expr) {
|
|
2158
|
-
return expr.any.some((subExpr) => evaluateRequirement(subExpr, wd, matchedEnvs));
|
|
2159
|
-
}
|
|
2160
|
-
if ("fileExists" in expr) {
|
|
2161
|
-
return isFile(path6.join(wd, expr.fileExists));
|
|
2162
|
-
}
|
|
2163
|
-
if ("fileNotExists" in expr) {
|
|
2164
|
-
return !isFile(path6.join(wd, expr.fileNotExists));
|
|
2165
|
-
}
|
|
2166
|
-
if ("dirExists" in expr) {
|
|
2167
|
-
return isDirectory(path6.join(wd, expr.dirExists));
|
|
2168
|
-
}
|
|
2169
|
-
if ("dirNotExists" in expr) {
|
|
2170
|
-
return !isDirectory(path6.join(wd, expr.dirNotExists));
|
|
2171
|
-
}
|
|
2172
|
-
if ("fileContains" in expr) {
|
|
2173
|
-
const filePath = path6.join(wd, expr.fileContains);
|
|
2174
|
-
if (!fs7.existsSync(filePath)) {
|
|
2175
|
-
return false;
|
|
2176
|
-
}
|
|
2177
|
-
try {
|
|
2178
|
-
const content = fs7.readFileSync(filePath, "utf-8");
|
|
2179
|
-
const regex = new RegExp(expr.pattern, expr.flags || "");
|
|
2180
|
-
return regex.test(content);
|
|
2181
|
-
} catch {
|
|
2182
|
-
return false;
|
|
2183
|
-
}
|
|
2184
|
-
}
|
|
2185
|
-
if ("environmentExists" in expr) {
|
|
2186
|
-
return matchedEnvs.has(expr.environmentExists);
|
|
2187
|
-
}
|
|
2188
|
-
if ("environmentNotExists" in expr) {
|
|
2189
|
-
return !matchedEnvs.has(expr.environmentNotExists);
|
|
2190
|
-
}
|
|
2191
|
-
return false;
|
|
2192
|
-
}
|
|
2193
|
-
function autoDetectLanguage(discovery, wd) {
|
|
2194
|
-
for (const [language, config2] of Object.entries(discovery)) {
|
|
2195
|
-
if (config2.require) {
|
|
2196
|
-
const matchedEnvs = /* @__PURE__ */ new Set();
|
|
2197
|
-
if (evaluateRequirement(config2.require, wd, matchedEnvs)) {
|
|
2198
|
-
return language;
|
|
2199
|
-
}
|
|
2200
|
-
}
|
|
2201
|
-
}
|
|
2202
|
-
return null;
|
|
2203
|
-
}
|
|
2204
|
-
async function selectLanguageManually(discovery, wd) {
|
|
2205
|
-
const languages = Object.keys(discovery);
|
|
2206
|
-
if (languages.length === 0) {
|
|
2207
|
-
p10.log.error(pc7.red("No languages available in discovery configuration."));
|
|
2208
|
-
throw new Error("No languages available");
|
|
2209
|
-
}
|
|
2210
|
-
const selected = await p10.select({
|
|
2211
|
-
message: "Select a language for your project",
|
|
2212
|
-
options: languages.map((lang) => ({ value: lang, label: lang }))
|
|
2213
|
-
});
|
|
2214
|
-
if (p10.isCancel(selected)) {
|
|
2215
|
-
p10.outro("Init cancelled.");
|
|
2216
|
-
process.exit(0);
|
|
2217
|
-
}
|
|
2218
|
-
const language = selected;
|
|
2219
|
-
const config2 = discovery[language];
|
|
2220
|
-
if (config2.require) {
|
|
2221
|
-
const matchedEnvs = /* @__PURE__ */ new Set();
|
|
2222
|
-
const requirementsMet = evaluateRequirement(config2.require, wd, matchedEnvs);
|
|
2223
|
-
if (!requirementsMet) {
|
|
2224
|
-
const errorMessage = config2.errorMessage || `Your project does not meet the requirements for ${language}.`;
|
|
2225
|
-
p10.log.warn(pc7.yellow(errorMessage));
|
|
2226
|
-
const continueAnyway = await p10.confirm({
|
|
2227
|
-
message: "Do you want to continue anyway?",
|
|
2228
|
-
initialValue: false
|
|
2229
|
-
});
|
|
2230
|
-
if (p10.isCancel(continueAnyway) || !continueAnyway) {
|
|
2231
|
-
return selectLanguageManually(discovery, wd);
|
|
2232
|
-
}
|
|
2233
|
-
}
|
|
2234
|
-
}
|
|
2235
|
-
return language;
|
|
2236
|
-
}
|
|
2237
|
-
async function fetchLanguageTemplate(baseUrl, language) {
|
|
2238
|
-
const data = await fetchJson(`${baseUrl}/${language}.json`, `${language} template`);
|
|
2239
|
-
const environments = {};
|
|
2240
|
-
if (data.environments) {
|
|
2241
|
-
for (const [envName, envConfig] of Object.entries(data.environments)) {
|
|
2242
|
-
environments[envName] = {
|
|
2243
|
-
require: envConfig.require,
|
|
2244
|
-
files: []
|
|
2245
|
-
};
|
|
2246
|
-
}
|
|
2247
|
-
}
|
|
2248
|
-
return {
|
|
2249
|
-
files: data.files || [],
|
|
2250
|
-
environments
|
|
2251
|
-
};
|
|
2252
|
-
}
|
|
2253
|
-
async function fetchEnvironmentTemplate(baseUrl, language, environment) {
|
|
2254
|
-
const url = `${baseUrl}/${language}-${environment}.json`;
|
|
2255
|
-
spinner6.start();
|
|
2256
|
-
const [response, fetchError] = await tryCatch(async () => {
|
|
2257
|
-
const res = await fetch(url);
|
|
2258
|
-
if (!res.ok) {
|
|
2259
|
-
if (res.status === 404) {
|
|
2260
|
-
return null;
|
|
2261
|
-
}
|
|
2262
|
-
throw new Error(`Failed to fetch environment template: ${res.status} ${res.statusText}`);
|
|
2263
|
-
}
|
|
2264
|
-
return res;
|
|
2265
|
-
});
|
|
2266
|
-
spinner6.stop();
|
|
2267
|
-
if (fetchError || !response) {
|
|
2268
|
-
if (fetchError) {
|
|
2269
|
-
p10.log.warn(pc7.yellow(`Failed to fetch ${language}-${environment} template: ${getErrorMessage(fetchError)}`));
|
|
2270
|
-
}
|
|
2271
|
-
return [];
|
|
2272
|
-
}
|
|
2273
|
-
const [data, parseError] = await tryCatch(async () => {
|
|
2274
|
-
const text5 = await response.text();
|
|
2275
|
-
return JSON.parse(text5);
|
|
2276
|
-
});
|
|
2277
|
-
if (parseError || !data) {
|
|
2278
|
-
p10.log.warn(pc7.yellow(`Failed to parse ${language}-${environment} template: ${getErrorMessage(parseError)}`));
|
|
2279
|
-
return [];
|
|
2280
|
-
}
|
|
2281
|
-
return data.files || [];
|
|
2282
|
-
}
|
|
2283
|
-
function hasEnvironmentNotExists(expr, targetEnv) {
|
|
2284
|
-
if ("environmentNotExists" in expr) {
|
|
2285
|
-
return expr.environmentNotExists === targetEnv;
|
|
2286
|
-
}
|
|
2287
|
-
if ("all" in expr || "any" in expr) {
|
|
2288
|
-
const subExprs = "all" in expr ? expr.all : expr.any;
|
|
2289
|
-
return subExprs.some((subExpr) => hasEnvironmentNotExists(subExpr, targetEnv));
|
|
2290
|
-
}
|
|
2291
|
-
return false;
|
|
2292
|
-
}
|
|
2293
|
-
function hasAnyEnvironmentNotExists(expr) {
|
|
2294
|
-
if ("environmentNotExists" in expr) {
|
|
2295
|
-
return true;
|
|
2296
|
-
}
|
|
2297
|
-
if ("all" in expr || "any" in expr) {
|
|
2298
|
-
const subExprs = "all" in expr ? expr.all : expr.any;
|
|
2299
|
-
return subExprs.some((subExpr) => hasAnyEnvironmentNotExists(subExpr));
|
|
2300
|
-
}
|
|
2301
|
-
return false;
|
|
2302
|
-
}
|
|
2303
|
-
function detectEnvironments(template, wd, matchedEnvs) {
|
|
2304
|
-
const initialMatches = [];
|
|
2305
|
-
const tempMatched = new Set(matchedEnvs);
|
|
2306
|
-
for (const [envName, envConfig] of Object.entries(template.environments)) {
|
|
2307
|
-
if (evaluateRequirement(envConfig.require, wd, tempMatched)) {
|
|
2308
|
-
initialMatches.push(envName);
|
|
2309
|
-
}
|
|
2310
|
-
}
|
|
2311
|
-
const sortedMatches = initialMatches.sort((a, b) => {
|
|
2312
|
-
const aHasConstraint = hasAnyEnvironmentNotExists(template.environments[a].require);
|
|
2313
|
-
const bHasConstraint = hasAnyEnvironmentNotExists(template.environments[b].require);
|
|
2314
|
-
if (aHasConstraint && !bHasConstraint) return 1;
|
|
2315
|
-
if (!aHasConstraint && bHasConstraint) return -1;
|
|
2316
|
-
return 0;
|
|
2317
|
-
});
|
|
2318
|
-
const finalMatches = [];
|
|
2319
|
-
const currentMatched = new Set(matchedEnvs);
|
|
2320
|
-
for (const envName of sortedMatches) {
|
|
2321
|
-
const envConfig = template.environments[envName];
|
|
2322
|
-
if (evaluateRequirement(envConfig.require, wd, currentMatched)) {
|
|
2323
|
-
const hasConflict = finalMatches.some(
|
|
2324
|
-
(selectedEnv) => hasEnvironmentNotExists(template.environments[selectedEnv].require, envName)
|
|
2325
|
-
);
|
|
2326
|
-
if (!hasConflict) {
|
|
2327
|
-
for (let i = finalMatches.length - 1; i >= 0; i--) {
|
|
2328
|
-
const existingEnv = finalMatches[i];
|
|
2329
|
-
if (hasEnvironmentNotExists(envConfig.require, existingEnv)) {
|
|
2330
|
-
finalMatches.splice(i, 1);
|
|
2331
|
-
currentMatched.delete(existingEnv);
|
|
2332
|
-
}
|
|
2333
|
-
}
|
|
2334
|
-
finalMatches.push(envName);
|
|
2335
|
-
currentMatched.add(envName);
|
|
2336
|
-
matchedEnvs.add(envName);
|
|
2337
|
-
}
|
|
2338
|
-
}
|
|
2339
|
-
}
|
|
2340
|
-
return finalMatches;
|
|
2341
|
-
}
|
|
2342
|
-
function writeTemplateFiles(files, wd) {
|
|
2343
|
-
for (const file of files) {
|
|
2344
|
-
const targetPath = resolvePathVariables(file.path, wd);
|
|
2345
|
-
const fullPath = path6.join(wd, targetPath);
|
|
2346
|
-
const dir = path6.dirname(fullPath);
|
|
2347
|
-
if (!fs7.existsSync(dir)) {
|
|
2348
|
-
fs7.mkdirSync(dir, { recursive: true });
|
|
2349
|
-
}
|
|
2350
|
-
try {
|
|
2351
|
-
fs7.writeFileSync(fullPath, file.content, "utf-8");
|
|
2352
|
-
} catch (error) {
|
|
2353
|
-
p10.log.warn(pc7.yellow(`Failed to write ${targetPath}: ${getErrorMessage(error)}`));
|
|
2354
|
-
}
|
|
2355
|
-
}
|
|
2356
|
-
}
|
|
2357
|
-
async function applyTemplates(language, wd, baseUrl) {
|
|
2358
|
-
const template = await fetchLanguageTemplate(baseUrl, language);
|
|
2359
|
-
const matchedEnvs = /* @__PURE__ */ new Set();
|
|
2360
|
-
let totalFilesWritten = 0;
|
|
2361
|
-
if (template.files.length > 0) {
|
|
2362
|
-
writeTemplateFiles(template.files, wd);
|
|
2363
|
-
totalFilesWritten += template.files.length;
|
|
2364
|
-
}
|
|
2365
|
-
if (Object.keys(template.environments).length > 0) {
|
|
2366
|
-
const matchingEnvs = detectEnvironments(template, wd, matchedEnvs);
|
|
2367
|
-
for (const envName of matchingEnvs) {
|
|
2368
|
-
const envFiles = await fetchEnvironmentTemplate(baseUrl, language, envName);
|
|
2369
|
-
if (envFiles.length > 0) {
|
|
2370
|
-
writeTemplateFiles(envFiles, wd);
|
|
2371
|
-
totalFilesWritten += envFiles.length;
|
|
2372
|
-
} else {
|
|
2373
|
-
p10.log.warn(pc7.yellow(`Environment ${envName} matched but has no files to apply`));
|
|
2374
|
-
}
|
|
2375
|
-
}
|
|
2376
|
-
}
|
|
2377
|
-
if (totalFilesWritten > 0) {
|
|
2378
|
-
p10.log.success(`Written ${pc7.cyan(totalFilesWritten.toString())} file(s) to quickly get started!`);
|
|
2379
|
-
}
|
|
2380
|
-
}
|
|
2381
|
-
|
|
2382
|
-
// src/commands/init/index.ts
|
|
2383
|
-
import * as p11 from "@clack/prompts";
|
|
2384
|
-
import { APIKeysApi as APIKeysApi2, TasksApi as TasksApi2 } from "@rightbrain/brain-api-client";
|
|
2385
|
-
import { execSync } from "child_process";
|
|
2386
|
-
import fs8 from "fs";
|
|
2387
|
-
import path7 from "path";
|
|
2388
|
-
import pc8 from "picocolors";
|
|
2389
|
-
var CONFIG_FILE_NAME2 = "rightbrain.json";
|
|
2390
|
-
var ENV_FILE = ".env";
|
|
2391
|
-
function registerInitCommand(program) {
|
|
2392
|
-
program.description("Initialize RightBrain configuration for your project").action(runInit);
|
|
2393
|
-
}
|
|
2394
|
-
var spinner7 = getSpinner();
|
|
2395
|
-
async function warnIfConfigFileExists(wd) {
|
|
2396
|
-
const configFilePath = path7.join(wd, CONFIG_FILE_NAME2);
|
|
2397
|
-
if (fs8.existsSync(configFilePath)) {
|
|
2398
|
-
p11.log.warn(pc8.yellow(`A ${CONFIG_FILE_NAME2} file already exists at ${wd}.`));
|
|
2399
|
-
const override = await p11.confirm({
|
|
2400
|
-
message: "Do you want to override the existing configuration?",
|
|
2401
|
-
initialValue: false
|
|
2402
|
-
});
|
|
2403
|
-
if (p11.isCancel(override) || !override) {
|
|
2404
|
-
p11.outro("Init cancelled.");
|
|
2405
|
-
process.exit(0);
|
|
2406
|
-
}
|
|
2407
|
-
}
|
|
2408
|
-
}
|
|
2409
|
-
async function warnIfGitRepoHasUnstagedChanges(wd) {
|
|
2410
|
-
const [isInsideGitRepo] = tryCatch(() => {
|
|
2411
|
-
execSync("git rev-parse --is-inside-work-tree", { cwd: wd, encoding: "utf-8", stdio: "pipe" });
|
|
2412
|
-
return true;
|
|
2413
|
-
});
|
|
2414
|
-
if (isInsideGitRepo) {
|
|
2415
|
-
const [, diffError] = tryCatch(() => execSync("git diff --quiet", { cwd: wd, encoding: "utf-8" }));
|
|
2416
|
-
const hasUnstagedChanges = !!diffError;
|
|
2417
|
-
const [statusOutput, statusError] = tryCatch(
|
|
2418
|
-
() => execSync("git status --porcelain", { cwd: wd, encoding: "utf-8" })
|
|
2419
|
-
);
|
|
2420
|
-
const hasUntrackedFiles = !statusError && statusOutput && statusOutput.split("\n").some((line) => line.trim().startsWith("??"));
|
|
2421
|
-
if (hasUnstagedChanges || hasUntrackedFiles) {
|
|
2422
|
-
const message = hasUnstagedChanges && hasUntrackedFiles ? "You have unstaged changes and untracked files in your repository." : hasUnstagedChanges ? "You have unstaged changes in your repository." : "You have untracked files in your repository.";
|
|
2423
|
-
p11.log.warn(pc8.yellow(message));
|
|
2424
|
-
const proceed = await p11.confirm({
|
|
2425
|
-
message: "Do you want to continue anyway?",
|
|
2426
|
-
initialValue: false
|
|
2427
|
-
});
|
|
2428
|
-
if (p11.isCancel(proceed) || !proceed) {
|
|
2429
|
-
p11.outro("Init cancelled. Please stage or stash your changes first.");
|
|
2430
|
-
process.exit(0);
|
|
2431
|
-
}
|
|
2432
|
-
}
|
|
2433
|
-
} else {
|
|
2434
|
-
p11.log.warn(pc8.yellow("No git repository detected.\nFiles generated in the next step will not be tracked."));
|
|
2435
|
-
}
|
|
2436
|
-
}
|
|
2437
|
-
async function detectLanguage() {
|
|
2438
|
-
let selectedLanguage;
|
|
2439
|
-
let outputDir;
|
|
2440
|
-
try {
|
|
2441
|
-
const discovery = await fetchDiscovery(buildEnv.CLI_PUBLIC_TEMPLATES_URL);
|
|
2442
|
-
const detectedLanguage = autoDetectLanguage(discovery, process.cwd());
|
|
2443
|
-
if (detectedLanguage) {
|
|
2444
|
-
const proceed = await p11.confirm({
|
|
2445
|
-
message: `Detected ${pc8.cyan(detectedLanguage)}, do you want to proceed with it?`,
|
|
2446
|
-
initialValue: true
|
|
2447
|
-
});
|
|
2448
|
-
if (p11.isCancel(proceed)) {
|
|
2449
|
-
p11.outro("Init cancelled.");
|
|
2450
|
-
process.exit(0);
|
|
2451
|
-
}
|
|
2452
|
-
if (proceed) {
|
|
2453
|
-
selectedLanguage = detectedLanguage;
|
|
2454
|
-
} else {
|
|
2455
|
-
selectedLanguage = await selectLanguageManually(discovery, process.cwd());
|
|
2456
|
-
}
|
|
2457
|
-
} else {
|
|
2458
|
-
selectedLanguage = await selectLanguageManually(discovery, process.cwd());
|
|
2459
|
-
}
|
|
2460
|
-
const languageConfig = discovery[selectedLanguage];
|
|
2461
|
-
outputDir = languageConfig?.outputDir;
|
|
2462
|
-
} catch (error) {
|
|
2463
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2464
|
-
p11.log.error(pc8.red(`Language detection failed: ${errorMessage}`));
|
|
2465
|
-
process.exit(1);
|
|
2466
|
-
}
|
|
2467
|
-
return { selectedLanguage, outputDir };
|
|
2468
|
-
}
|
|
2469
|
-
async function selectProjectInitProject() {
|
|
2470
|
-
const context = await getAuthContext({ loadGlobalConfig: true });
|
|
2471
|
-
const selectedProject = await selectProject({ accessToken: context.accessToken });
|
|
2472
|
-
return { selectedProject, context };
|
|
2473
|
-
}
|
|
2474
|
-
async function createNewApiKey(apiKeysApi, orgId, projectId) {
|
|
2475
|
-
const keyName = await p11.text({
|
|
2476
|
-
message: "Enter a name for the new API key",
|
|
2477
|
-
placeholder: "my-api-key",
|
|
2478
|
-
validate: (value) => {
|
|
2479
|
-
if (!value || value.trim().length === 0) {
|
|
2480
|
-
return "API key name is required";
|
|
2481
|
-
}
|
|
2482
|
-
return void 0;
|
|
2483
|
-
}
|
|
2484
|
-
});
|
|
2485
|
-
if (p11.isCancel(keyName)) {
|
|
2486
|
-
p11.outro("Init cancelled.");
|
|
2487
|
-
process.exit(0);
|
|
2488
|
-
}
|
|
2489
|
-
spinner7.start();
|
|
2490
|
-
const [newKey, createError] = await tryCatch(async () => {
|
|
2491
|
-
const response = await apiKeysApi.createApiKey(orgId, projectId, { name: keyName });
|
|
2492
|
-
return response.data;
|
|
2493
|
-
});
|
|
2494
|
-
spinner7.stop();
|
|
2495
|
-
if (createError || !newKey) {
|
|
2496
|
-
p11.log.error(pc8.red("Failed to create API key."));
|
|
2497
|
-
return null;
|
|
2498
|
-
}
|
|
2499
|
-
p11.log.success(`Created API key: ${pc8.cyan(newKey.name)}`);
|
|
2500
|
-
return { id: newKey.id, name: newKey.name, key: newKey.key };
|
|
2501
|
-
}
|
|
2502
|
-
async function selectApiKey({ selectedProject }) {
|
|
2503
|
-
const context = await getAuthContext({ loadGlobalConfig: true });
|
|
2504
|
-
spinner7.start();
|
|
2505
|
-
const apiKeysApi = new APIKeysApi2(getConfiguration(context.accessToken));
|
|
2506
|
-
const [apiKeys, apiKeysError] = await tryCatch(async () => {
|
|
2507
|
-
const response = await apiKeysApi.listApiKeys(selectedProject.orgId, selectedProject.projectId);
|
|
2508
|
-
return response.data;
|
|
2509
|
-
});
|
|
2510
|
-
spinner7.stop();
|
|
2511
|
-
let selectedApiKey = null;
|
|
2512
|
-
let apiKeysFailed = false;
|
|
2513
|
-
if (apiKeysError) {
|
|
2514
|
-
p11.log.warn(pc8.yellow("Unable to fetch API keys.\nVisit dashboard to create or retrieve your API key."));
|
|
2515
|
-
apiKeysFailed = true;
|
|
2516
|
-
}
|
|
2517
|
-
const activeApiKeys = (apiKeys || []).filter((key) => !key.revoked);
|
|
2518
|
-
if (activeApiKeys.length > 0) {
|
|
2519
|
-
const CREATE_NEW_KEY = "__create_new__";
|
|
2520
|
-
const apiKeyChoice = await p11.autocomplete({
|
|
2521
|
-
message: "Select an API key to use",
|
|
2522
|
-
options: [
|
|
2523
|
-
...activeApiKeys.map((key) => ({
|
|
2524
|
-
value: key.id,
|
|
2525
|
-
label: key.name,
|
|
2526
|
-
hint: `Created: ${new Date(key.created).toLocaleDateString()}`
|
|
2527
|
-
})),
|
|
2528
|
-
{
|
|
2529
|
-
value: CREATE_NEW_KEY,
|
|
2530
|
-
label: "Create a new API key",
|
|
2531
|
-
hint: "Generate a new API key for this project"
|
|
2532
|
-
}
|
|
2533
|
-
]
|
|
2534
|
-
});
|
|
2535
|
-
if (p11.isCancel(apiKeyChoice)) {
|
|
2536
|
-
p11.outro("Init cancelled.");
|
|
2537
|
-
process.exit(0);
|
|
2538
|
-
}
|
|
2539
|
-
if (apiKeyChoice === CREATE_NEW_KEY) {
|
|
2540
|
-
selectedApiKey = await createNewApiKey(apiKeysApi, selectedProject.orgId, selectedProject.projectId);
|
|
2541
|
-
} else {
|
|
2542
|
-
const foundKey = activeApiKeys.find((k) => k.id === apiKeyChoice);
|
|
2543
|
-
if (foundKey) {
|
|
2544
|
-
selectedApiKey = { id: foundKey.id, name: foundKey.name, key: foundKey.key };
|
|
2545
|
-
}
|
|
2546
|
-
}
|
|
2547
|
-
} else {
|
|
2548
|
-
selectedApiKey = await createNewApiKey(apiKeysApi, selectedProject.orgId, selectedProject.projectId);
|
|
2549
|
-
}
|
|
2550
|
-
return { selectedApiKey, apiKeysFailed };
|
|
2551
|
-
}
|
|
2552
|
-
async function generateConfig({
|
|
2553
|
-
wd,
|
|
2554
|
-
configFilePath,
|
|
2555
|
-
selectedApiKey,
|
|
2556
|
-
selectedProject,
|
|
2557
|
-
language,
|
|
2558
|
-
outputDir
|
|
2559
|
-
}) {
|
|
2560
|
-
let adjustedOutputDir = resolvePathVariables(outputDir, wd);
|
|
2561
|
-
if (!adjustedOutputDir.startsWith("./")) {
|
|
2562
|
-
adjustedOutputDir = `./${adjustedOutputDir}`;
|
|
2563
|
-
}
|
|
2564
|
-
const absoluteOutputDir = path7.join(wd, adjustedOutputDir);
|
|
2565
|
-
if (!fs8.existsSync(absoluteOutputDir)) {
|
|
2566
|
-
fs8.mkdirSync(absoluteOutputDir, { recursive: true });
|
|
2567
|
-
}
|
|
2568
|
-
const configContent = {
|
|
2569
|
-
$schema: buildEnv.CLI_PUBLIC_TEMPLATES_URL + "/schema.json",
|
|
2570
|
-
orgId: "${RB_ORG_ID}",
|
|
2571
|
-
projectId: "${RB_PROJECT_ID}",
|
|
2572
|
-
apiKey: "${RB_API_KEY}",
|
|
2573
|
-
generate: {
|
|
2574
|
-
language,
|
|
2575
|
-
outputDir: adjustedOutputDir,
|
|
2576
|
-
taskIds: []
|
|
2577
|
-
}
|
|
2578
|
-
};
|
|
2579
|
-
fs8.writeFileSync(configFilePath, JSON.stringify(configContent, null, 2) + "\n", "utf-8");
|
|
2580
|
-
const envFilePath = path7.join(wd, ENV_FILE);
|
|
2581
|
-
const apiKeyValue = selectedApiKey?.key || "<YOUR_API_KEY>";
|
|
2582
|
-
const envContent = `# RightBrain Configuration
|
|
2583
|
-
RB_ORG_ID="${selectedProject.orgId}"
|
|
2584
|
-
RB_PROJECT_ID="${selectedProject.projectId}"
|
|
2585
|
-
RB_API_KEY="${apiKeyValue}"
|
|
2586
|
-
`;
|
|
2587
|
-
let isEnvFileUpdated = false;
|
|
2588
|
-
if (fs8.existsSync(envFilePath)) {
|
|
2589
|
-
const existingEnv = fs8.readFileSync(envFilePath, "utf-8");
|
|
2590
|
-
const separator = existingEnv.endsWith("\n") ? "" : "\n";
|
|
2591
|
-
fs8.appendFileSync(envFilePath, `${separator}${envContent}`, "utf-8");
|
|
2592
|
-
isEnvFileUpdated = true;
|
|
2593
|
-
} else {
|
|
2594
|
-
fs8.writeFileSync(envFilePath, envContent, "utf-8");
|
|
2595
|
-
}
|
|
2596
|
-
return { isEnvFileUpdated, outputDir: adjustedOutputDir, configContent };
|
|
2597
|
-
}
|
|
2598
|
-
async function selectTasks({
|
|
2599
|
-
accessToken: accessToken2,
|
|
2600
|
-
orgId,
|
|
2601
|
-
projectId,
|
|
2602
|
-
config: config2,
|
|
2603
|
-
configFilePath
|
|
2604
|
-
}) {
|
|
2605
|
-
const tasksApi = new TasksApi2(getConfiguration(accessToken2));
|
|
2606
|
-
spinner7.start();
|
|
2607
|
-
const [tasksResult, tasksError] = await tryCatch(async () => {
|
|
2608
|
-
const response = await tasksApi.listTasks(orgId, projectId);
|
|
2609
|
-
return response.data.results;
|
|
2610
|
-
});
|
|
2611
|
-
spinner7.stop();
|
|
2612
|
-
if (tasksError) {
|
|
2613
|
-
p11.log.warn(pc8.yellow("Unable to fetch tasks.\nYou can manually add task IDs to your config later."));
|
|
2614
|
-
return false;
|
|
2615
|
-
}
|
|
2616
|
-
const tasks = tasksResult || [];
|
|
2617
|
-
if (tasks.length === 0) {
|
|
2618
|
-
p11.log.info("No tasks found in this project. You can add task IDs later.");
|
|
2619
|
-
return false;
|
|
2620
|
-
}
|
|
2621
|
-
const selectedTasks = await p11.autocompleteMultiselect({
|
|
2622
|
-
message: "Select tasks for type generation (type to filter, tab to select)",
|
|
2623
|
-
options: tasks.map((task) => ({
|
|
2624
|
-
value: task.id,
|
|
2625
|
-
label: task.name,
|
|
2626
|
-
hint: task.description ? task.description.slice(0, 50) + (task.description.length > 50 ? "..." : "") : void 0
|
|
2627
|
-
})),
|
|
2628
|
-
required: false
|
|
2629
|
-
});
|
|
2630
|
-
if (p11.isCancel(selectedTasks) || selectedTasks.length === 0) {
|
|
2631
|
-
p11.log.info("No tasks selected. You can add task IDs later.");
|
|
2632
|
-
return false;
|
|
2633
|
-
}
|
|
2634
|
-
const taskMap = new Map(tasks.map((t) => [t.id, t.name]));
|
|
2635
|
-
const taskIdsWithComments = selectedTasks.map((id) => ` /* ${taskMap.get(id)} */ "${id}"`).join(",\n");
|
|
2636
|
-
const configWithComments = `{
|
|
2637
|
-
"$schema": "${config2.$schema}",
|
|
2638
|
-
"orgId": "${config2.orgId}",
|
|
2639
|
-
"projectId": "${config2.projectId}",
|
|
2640
|
-
"apiKey": "${config2.apiKey}",
|
|
414
|
+
</html>`}function qr(e){let t={"&":"&","<":"<",">":">",'"':""","'":"'"};return e.replace(/[&<>"']/g,r=>t[r]||r)}var $e=27246;function Rt(){return `http://localhost:${$e}/login`}function gt(){return `http://localhost:${$e}/callback`}async function Vr(e,t){let r=gt(),o=`${J.oauthUrl}${J.tokenPath}`,n=new URLSearchParams({grant_type:"authorization_code",code:e,redirect_uri:r,client_id:J.clientId});t&&n.append("code_verifier",t);let i=await fetch(o,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:n.toString()});if(!i.ok)throw new Error(`Token exchange failed: ${i.status} - ${await i.text()}`);return await i.json()}function Dt(e,t,r){return new Promise(o=>{let n=Kr.createServer(async(s,a)=>{let c=new URL(s.url||"/",`http://localhost:${$e}`);if(c.pathname==="/login"){a.writeHead(302,{Location:r}),a.end();return}if(c.pathname!=="/callback"){a.writeHead(404,{"Content-Type":"text/plain"}),a.end("Not Found");return}let u=c.searchParams.get("code"),l=c.searchParams.get("state"),d=c.searchParams.get("error"),v=c.searchParams.get("error_description");if(d){let h=v||d;a.writeHead(400,{"Content-Type":"text/html"}),a.end(Ae(h)),n.close(),o({success:false,error:h});return}if(l!==e){let h="State mismatch - possible CSRF attack";a.writeHead(400,{"Content-Type":"text/html"}),a.end(Ae(h)),n.close(),o({success:false,error:h});return}if(!u){let h="No authorization code received";a.writeHead(400,{"Content-Type":"text/html"}),a.end(Ae(h)),n.close(),o({success:false,error:h});return}let[E,I]=await p(()=>Vr(u,t));if(I||!E){let h=I instanceof Error?I.message:"Token exchange failed";a.writeHead(500,{"Content-Type":"text/html"}),a.end(Ae(h)),n.close(),o({success:false,error:h});return}a.writeHead(200,{"Content-Type":"text/html"}),a.end(Ft()),n.close(),o({success:true,token:E});});n.listen($e),n.on("error",s=>{o({success:false,error:`Failed to start callback server on port ${$e}: ${s.message}`});});let i=setTimeout(()=>{n.close(),o({success:false,error:"Login timeout - please try again"});},120*1e3);n.on("close",()=>{clearTimeout(i);});})}var f=!process.stdout.isTTY;var Ne="rightbrain.json",yt=".env",ue=null;function Ot(e){te.existsSync(e)||((f?console.error:m.outro)(j.red(`Configuration file not found: ${e}`)),process.exit(1)),te.statSync(e).isDirectory()&&((f?console.error:m.outro)(j.red(`Invalid configuration path: ${e}
|
|
415
|
+
Expected a file (e.g., ${Ne}), but received a directory.`)),process.exit(1)),ue=e;}var Yr=z.object({$schema:z.string().optional(),orgId:z.string().refine(H,{message:"Invalid UUID format"}),projectId:z.string().refine(H,{message:"Invalid UUID format"}),envFile:z.string().default(yt).optional(),apiKey:z.string().optional(),generate:z.object({language:z.string(),outputDir:z.string().refine(e=>{if(!e)return true;let t=e.replace(/^\.\//,"").replace(/\/$/,"");return !(t===""||t===".")},{message:'outputDir cannot be project root ("./" or "."). It must be inside the project below rightbrain.json level.'}),taskIds:z.array(z.string().refine(H,{message:"Invalid UUID format"})),hardcodeTaskIds:z.boolean().default(false).optional()}).optional(),clientId:z.string().refine(H,{message:"Invalid UUID format"}).optional(),clientSecret:z.string().optional(),oauthUrl:z.url().optional(),apiUrl:z.url().default(b.CLI_PUBLIC_API_URL).optional()}).refine(e=>{let t=e.clientId&&e.clientSecret,r=!!e.apiKey;return t||r},{message:"Either 'apiKey' or both 'clientId' and 'clientSecret' must be provided to authenticate"});function Zr(){return ue?X.dirname(ue):process.cwd()}function Gr(e=yt,t=false){let r=Zr(),o=X.isAbsolute(e),n=o?e:X.join(r,e);if(te.existsSync(n)){if(!te.statSync(n).isFile())throw new Error(`envFile path "${e}" exists but is not a file (it's a directory).`);Bt.expand(Ut.config({path:n,override:true}));return}if(!o){let i=X.join(r,`${e}.local`);if(te.existsSync(i)){if(!te.statSync(i).isFile())throw new Error(`envFile path "${e}.local" exists but is not a file (it's a directory).`);Bt.expand(Ut.config({path:i,override:true}));return}}if(t){let i=o?[n]:[n,X.join(r,`${e}.local`)];throw new Error(`envFile "${e}" specified in config but not found. Expected at: ${i.join(" or ")}`)}}function Hr(e){let t="",r=0;for(;r<e.length;)if(e[r]==="$"&&e[r+1]==="{"){let o=1,n=r+2,i=false;for(;n<e.length&&o>0;)e[n]==="$"&&e[n+1]==="{"?(i=true,o++,n++):e[n]==="}"&&o--,n++;if(o===0){let s=e.slice(r+2,n-1);if(i)throw new Error(`\${${s}}: bad substitution`);{let a=s.trim();t+=process.env[a]??"",r=n;}}else t+=e[r],r++;}else t+=e[r],r++;return t}function ht(e){if(typeof e=="string")return Hr(e);if(Array.isArray(e))return e.map(ht);if(e!==null&&typeof e=="object"){let t={};for(let[r,o]of Object.entries(e))t[r]=ht(o);return t}return e}function Jr(){if(ue)return ue;let e=X.join(process.cwd(),Ne);return te.existsSync(e)?e:(ue=X.join(process.cwd(),Ne),ue)}function Xr(){let e=Jr();if(!te.existsSync(e))return null;let t=te.readFileSync(e,"utf-8");return {config:JSON.parse(Wr(t)),filepath:e}}var Oe=null;function je(){if(Oe)return Oe;let[e,t]=p(Xr);if(t&&((f?console.error:m.outro)(j.red(`Error loading configuration file: ${t}`)),process.exit(1)),!e)return null;let r=e.config,o=yt,n=false;r&&typeof r=="object"&&r!==null&&"envFile"in r&&typeof r.envFile=="string"&&(o=r.envFile,n=true);let[i]=p(()=>{Gr(o,n);});i&&((f?console.error:m.outro)(j.red(`Error loading env file: ${i}`)),process.exit(1));let[s,a]=p(()=>ht(e.config));a&&((f?console.error:m.outro)(j.red(`Error resolving environment variables: ${a}`)),process.exit(1));let c=Yr.safeParse(s);if(c.success||((f?console.error:m.log.error)("Invalid Configuration: "),(f?console.error:m.log.error)(z.prettifyError(c.error)),(f?console.error:m.outro)(j.red("Please check your configuration file and try again.")),process.exit(1)),c.data.generate?.outputDir){let u=X.dirname(e.filepath),l=c.data.generate.outputDir,d=X.resolve(u,l),v=X.relative(u,d);(v===""||v==="."||v.startsWith(".."))&&((f?console.error:m.log.error)(j.red(`Invalid outputDir: "${l}" resolves to "${v}" which is outside the project or at project root. outputDir must be inside the project below rightbrain.json level.`)),process.exit(1));}return Oe={...c.data,filePath:e.filepath,rawConfig:r},Oe}function Nt(){let e=je();return e||((f?console.error:m.outro)(j.red(`No configuration file found. Expected: ${Ne}`)),process.exit(1)),e}var kt=X.join(Qr.homedir(),".rightbrain"),we=X.join(kt,"credentials.json"),ke=null;function me(){if(ke)return ke;if(!te.existsSync(we))return null;try{let e=te.readFileSync(we,"utf-8"),t=JSON.parse(e);return t.access_token?(ke=t,ke):null}catch{return null}}function eo(){te.existsSync(kt)||te.mkdirSync(kt,{recursive:true,mode:448});}function re(e,t=false){eo();let r=t?e:{...me(),...e};te.writeFileSync(we,JSON.stringify(r,null,2),"utf-8"),te.chmodSync(we,384),ke=r;}function qt(){te.existsSync(we)&&te.unlinkSync(we),ke=null;}function Kt(e){if(!e.expires_at)return false;let t=300*1e3;return Date.now()>=e.expires_at-t}var ve=null,xe=null,ze=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"],Me=0,Vt=process.stderr.isTTY,to=()=>{Vt&&process.stderr.write("\x1B[?25l");},qe=()=>{Vt&&process.stderr.write("\x1B[?25h");};process.on("exit",qe);process.on("SIGINT",()=>{qe(),process.exit(130);});process.on("SIGTERM",()=>{qe(),process.exit(143);});var ro=()=>{let e=false;return {start:()=>{e||(e=true,Me=0,xe&&clearInterval(xe),to(),process.stderr.write("\r"+ze[Me]),xe=setInterval(()=>{Me=(Me+1)%ze.length,process.stderr.write("\r"+ze[Me]);},80));},stop:()=>{xe&&(clearInterval(xe),xe=null),e&&(process.stderr.write("\r"+" ".repeat(ze[0].length)+"\r"),qe()),e=false;}}},_=()=>(ve||(ve=ro()),ve),Ke=()=>{ve&&(ve.stop(),ve=null);};async function W(e){let t=e?.loadGlobalConfig?null:je();if(!t){let o=me();if(o)if(Kt(o)){let n=await io(o);if(n)return {accessToken:n.access_token,orgId:n.org_id,projectId:n.project_id}}else return {accessToken:o.access_token,orgId:o.org_id,projectId:o.project_id};Ke(),(f?console.error:m.outro)("Not authenticated. Please run `npx rightbrain login` command to authenticate."),process.exit(1);}return {accessToken:await no(t),orgId:t.orgId,projectId:t.projectId,filePath:t.filePath}}var Ve=null;async function no(e){if(e.apiKey)return e.apiKey;if((!e.clientId||!e.clientSecret)&&(Ke(),(f?console.error:m.outro)(j.red("Client ID and secret are required to authenticate via OAuth")),process.exit(1)),!Ve||Ve.expired()){let t=new ClientCredentials({auth:{tokenHost:e.oauthUrl??b.CLI_PUBLIC_RB_OAUTH2_URL,tokenPath:"/oauth2/token"},client:{id:e.clientId,secret:e.clientSecret},http:{json:"force"}}),[r,o]=await p(()=>t.getToken({}));return (o||!r?.token.access_token)&&(Ke(),(f?console.error:m.outro)(j.red("Failed to obtain access token")),process.exit(1)),Ve=r,r.token.access_token}return Ve.token.access_token}var J={oauthUrl:b.CLI_PUBLIC_RB_OAUTH2_URL,authPath:"/oauth2/auth",tokenPath:"/oauth2/token",clientId:b.CLI_PUBLIC_RB_OAUTH2_CLIENT_ID};async function io(e){if(!e.refresh_token)return null;let t=`${J.oauthUrl}${J.tokenPath}`,r=new URLSearchParams({grant_type:"refresh_token",refresh_token:e.refresh_token,client_id:J.clientId});try{let o=await fetch(t,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:r.toString()});if(!o.ok)return null;let n=await o.json(),i=n.expires_in?Date.now()+n.expires_in*1e3:void 0,s={...e,access_token:n.access_token,refresh_token:n.refresh_token??e.refresh_token,expires_at:i};return re(s,!0),s}catch{return null}}function Yt(){return wt.randomBytes(32).toString("base64url")}async function Zt(e){return wt.createHash("sha256").update(e).digest().toString("base64url")}function Gt(e,t){let r=gt(),o=new URLSearchParams({client_id:J.clientId,redirect_uri:r,response_type:"code",scope:"offline_access",state:e});return t&&(o.append("code_challenge",t),o.append("code_challenge_method","S256")),`${J.oauthUrl}${J.authPath}?${o.toString()}`}function Ht(){return wt.randomBytes(32).toString("hex")}function L(e){if(f||m.outro("Something went wrong"),isAxiosError(e)){let t=e.response;if(t?.data){t.status===401&&console.error("Unauthorized Access please login again."),console.error("Error:"),console.info(f?JSON.stringify(t.data,null,2):inspect(t.data,{depth:null,colors:true}));return}if(t?.status){console.error(`Error: Request failed with status ${t.status}`);return}}if(e instanceof Error){console.error(e.message);return}(f?console.error:m.outro)("An unexpected error occurred");}async function de({accessToken:e,orgId:t,projectId:r}){let o=_();if(!t){o.start();let[n,i]=await p(async()=>await new OrganizationsApi(O(e)).listOrganizations().then(a=>a.data.results));if((i||!n)&&(o.stop(),L(i),process.exit(1)),o.stop(),n.length===0&&(m.cancel("No organizations found. Please create one first."),process.exit(1)),n.length===1)t=n[0].id,m.log.info(`Using organization: ${j.cyan(n[0].name)}`);else {let s=await m.select({message:"Select an organization",options:n.map(a=>({value:a.id,label:a.name,hint:a.id}))});m.isCancel(s)&&(m.outro("Select project cancelled"),process.exit(0)),t=s;}}if(!r){o.start();let[n,i]=await p(async()=>await new ProjectsApi(O(e)).listProjects(t).then(a=>a.data.results));if((i||!n)&&(o.stop(),L(i),process.exit(1)),o.stop(),n.length===0&&(m.cancel("No projects found. Please create one first."),process.exit(1)),n.length===1)r=n[0].id,m.log.info(`Using project: ${j.cyan(n[0].name)}`);else {let s=await m.select({message:"Select a project",options:n.map(a=>({value:a.id,label:a.name,hint:a.id}))});m.isCancel(s)&&(m.outro("Select project cancelled"),process.exit(0)),r=s;}}return (typeof t!="string"||typeof r!="string")&&(m.cancel("Failed to select project"),process.exit(1)),{orgId:t,projectId:r}}var O=e=>new Configuration({basePath:b.CLI_PUBLIC_API_URL,accessToken:e}),T=class e{_options;tasksApi;listingsApi;organizationsApi;projectsApi;apiKeysApi;constructor(t){this._options=t;let r=O(t.accessToken);this.tasksApi=new TasksApi(r),this.listingsApi=new ListingsApi(r),this.organizationsApi=new OrganizationsApi(r),this.projectsApi=new ProjectsApi(r),this.apiKeysApi=new APIKeysApi(r);}get orgId(){return this._options.orgId}get projectId(){return this._options.projectId}getTaskUrl(t){return `${b.CLI_PUBLIC_API_URL}/org/${this.orgId}/project/${this.projectId}/task/${t}`}static async fromAuthContext(){let t=await W(),r=await de(t);return (!t.orgId||!t.projectId)&&re({access_token:t.accessToken,org_id:r.orgId,project_id:r.projectId}),new e({...t,...r})}static async fromConfig(){let t=await W();return (!t.orgId||!t.projectId)&&((f?console.error:m.outro)("orgId and projectId are required to authenticate."),process.exit(1)),new e({accessToken:t.accessToken,orgId:t.orgId,projectId:t.projectId})}async listModels(){return (await this.listingsApi.getAllModels(this.orgId,this.projectId)).data}async createTask(t){return (await this.tasksApi.createTask(this.orgId,this.projectId,t)).data}async getOrganization(t){return (await this.organizationsApi.getOrganization(t)).data}async getProject(t,r){return (await this.projectsApi.getProject(t,r)).data}async listTasks(){return (await this.tasksApi.listTasks(this.orgId,this.projectId)).data.results}async getTask(t){return (await this.tasksApi.getTask(this.orgId,this.projectId,t,null,true)).data}async runTask({body:t,taskId:r,revisionId:o,useFallbackModel:n=false}){let i=new FormData;if(i.append("task_input",JSON.stringify(t.task_input)),t.task_files&&t.task_files.length>0)for(let c of t.task_files)i.append("task_file",c,c.name);let s=new URLSearchParams;return o&&s.set("revision_id",o),(await this.tasksApi.runTask(this.orgId,this.projectId,r,o,null,null,n,{data:i})).data}async listApiKeys(){return (await this.apiKeysApi.listApiKeys(this.orgId,this.projectId)).data}async createApiKey(t){return (await this.apiKeysApi.createApiKey(this.orgId,this.projectId,{name:t})).data}};var Q=e=>{let{canRunWithOptions:t=true}=e||{};process.env.TERM_PROGRAM?.toLowerCase().includes("mintty")&&(console.warn(`WARNING: It looks like you are using MinTTY, which is non-interactive. This is most likely because you are
|
|
416
|
+
using Git Bash. If that's that case, please use Git Bash from another terminal, such as Windows Terminal.
|
|
417
|
+
`),t&&console.warn("Alternatively, you can provide the arguments from the CLI directly to skip the prompts."),process.exit(1));};var Ye=async e=>{let t=await yo(e);await new Promise(r=>{t?.addListener("exit",()=>{r();}),setTimeout(()=>{r();},3e3);});};function Y(e){return e.startsWith("~/")?X.join(Qr.homedir(),e.slice(2)):X.resolve(e)}function wo(e){return te.existsSync(e)&&te.statSync(e).isDirectory()}function Ze(e,t){let r="$SRC/",o="$PWD/",n=X.join(t,"src"),i=wo(n);if(e.startsWith(r)){let s=i?"./src":"./",a=e.slice(r.length);return s+a}return e.startsWith(o)?"./"+e.slice(o.length):e}var Ge=e=>{let t=false,r=()=>{t||(t=true,process.stdin.removeListener("data",o));},o=n=>{let i=n.toString();(i.includes("\r")||i.includes(`
|
|
418
|
+
`))&&(r(),e());};return process.stdin.on("data",o),r};function tr(e){let t=te.readFileSync(e,"utf8");return parseDocument(t).toJS()}function rr(e){return !!e.model_params?.temperature}function bt(e,t){let r=te.readFileSync(e,"utf8"),o=parseDocument(r);if(t.model){o.set("llm_model_id",t.model.id);let i=o.get("llm_model_id",true);if(isScalar(i)&&(i.commentBefore=" Selected model: "+t.model.name),rr(t.model)){let s=o.get("llm_config",true),a=`Temperature config is not available to selected model
|
|
419
|
+
temperature: `;if(isMap(s)&&s.commentBefore?.startsWith(a)){let c=s.commentBefore.slice(a.length).trim(),u=parseFloat(c);isNaN(u)||s.set("temperature",u),s.commentBefore=void 0;}}else {let s=o.get("llm_config",true);if(isMap(s)&&s.has("temperature")){let a=s.get("temperature");s.commentBefore=`Temperature config is not available to selected model
|
|
420
|
+
temperature: ${a}`,s.delete("temperature");}}}t.taskName&&o.set("name",t.taskName);let n=o.toString();te.writeFileSync(e,n,"utf8");}async function vo(){let[e,t]=await p(()=>T.fromAuthContext());if(t||!e)return null;let[r,o]=await p(()=>e.listModels());if(o||!r)return null;let i=r.filter(a=>!a.replaced_by);return i.length===0?null:i.find(a=>a.provider.toLowerCase()==="openai")||i[0]}async function or(e){let t=X.dirname(e),[,r]=p(()=>{t&&t!=="."&&!te.existsSync(t)&&te.mkdirSync(t,{recursive:!0});});if(r){let c=r instanceof Error?r.message:String(r);(f?console.error:m.outro)(`Failed to create directory "${t}": ${c}`),process.exit(1);}let o=`${b.CLI_PUBLIC_TEMPLATES_URL}/create-task.yaml`,n=await fetch(o,{headers:{accept:"text/yaml"},redirect:"manual"});n.ok||((f?console.error:m.outro)(`Failed to download template: Server returned ${n.status} ${n.statusText}`),process.exit(1));let i=await n.text(),s=await vo();if(s){let c=parseDocument(i);c.set("llm_model_id",s.id);let u=c.get("llm_model_id",true);if(isScalar(u)&&(u.commentBefore=" Selected model: "+s.name),!rr(s)){let l=c.get("llm_config",true);if(isMap(l)&&l.has("temperature")){let d=l.get("temperature");l.commentBefore=`Temperature config is not available to selected model
|
|
421
|
+
temperature: ${d}`,l.delete("temperature");}}i=c.toString();}let[,a]=p(()=>te.writeFileSync(e,i,"utf8"));if(a){let c=a instanceof Error?a.message:String(a);(f?console.error:m.outro)(`Failed to write file "${e}": ${c}`),process.exit(1);}}function ir(e){e.description("Create a new task").option("-f, --file <file>","Create task from a YAML file.").action(bo);}async function bo(e){if(ee.start(),await T.fromAuthContext(),ee.stop(),!e.file){Q();let t=await m.confirm({message:"Create tasks directly from the dashboard",initialValue:true});m.isCancel(t)?(m.outro("Create task cancelled"),process.exit(0)):t?await Eo():await nr(e);return}await nr(e);}var ee=_();async function Eo(){let e=await T.fromAuthContext();ee.start();let[t]=await p(()=>e.getProject(e.orgId,e.projectId));ee.stop();let r=t?.name||e.projectId;m.log.info(`Opening dashboard to create task in project: ${j.cyan(r)}`);let o=m.spinner();o.start("Press Enter to open dashboard..."),Ge(async()=>{o.message("Opening dashboard...");let n=`${b.CLI_PUBLIC_TEMPLATES_URL}/server/org/${e.orgId}/project/${e.projectId}/task/create`,[,i]=await p(()=>Ye(n));i&&(o.stop("Could not open browser automatically."),m.outro(`Please open the URL manually: ${j.cyan(n)}`),process.exit(1)),o.stop("Dashboard opened in your browser"),m.outro(j.green("Please create the task in the dashboard.")),process.exit(0);});}var Et="create-task.yaml";async function nr({file:e}){if(e){let n=Y(e),i=null,s=null,a=null;for(;;){ee.start(),a=await T.fromAuthContext();let l=a,[d,v]=p(()=>tr(n)),[E,I]=await p(()=>l.createTask(d));if(ee.stop(),v&&(L(v),process.exit(1)),I){(f||!isAxiosError(I))&&(L(I),process.exit(1));let h=I.response?.data;if(typeof h=="object"&&h!==null&&"detail"in h&&Array.isArray(h.detail)){let U=h.detail;if(Array.isArray(U)&&U.length>0){if(U.find(w=>typeof w=="object"&&w!==null&&"type"in w&&w.type==="invalid_llm_model")){m.log.error("Invalid LLM model ID detected."),ee.start();let[w,C]=await p(()=>l.listModels());ee.stop(),(C||!w)&&(m.cancel(`Error: Failed to fetch available models. Please run ${j.cyan("npx rightbrain list-models")} to see available models.`),process.exit(1));let P=w.filter(G=>!G.replaced_by);P.length===0&&(m.cancel("Error: No available models found."),process.exit(1));let q=Ue(P),ae=Array.from(q.keys()).sort(),ce=[];for(let G of ae){let Ur=q.get(G);for(let mt of Ur)ce.push({value:mt.id,label:`${mt.alias} (${G})`,hint:mt.id});}let De=await m.autocomplete({message:"Please select a valid LLM model:",options:ce});m.isCancel(De)&&(m.outro("Model selection cancelled"),process.exit(0));let ut=P.find(G=>G.id===De);ut||(m.cancel("Error: Selected model not found."),process.exit(1));try{bt(n,{model:ut}),m.log.info(`Updated ${j.cyan(e)} with model: ${j.cyan(ut.alias)}`);}catch(G){m.cancel(`Error: Failed to update file: ${G instanceof Error?G.message:String(G)}`),process.exit(1);}continue}if(U.find(w=>typeof w=="object"&&w!==null&&"type"in w&&w.type==="duplicate_task_name")){m.log.error("Duplicate task name detected.");let w=await m.text({message:"Please enter a unique task name:",placeholder:"Enter task name",initialValue:d&&typeof d=="object"&&"name"in d&&typeof d.name=="string"&&d?.name||"",validate(C){if(typeof C=="string"&&C.trim()===".")return "File path cannot be empty";if(typeof C=="string"&&C.trim()===d?.name)return "Task name cannot be the same as the existing task"}});m.isCancel(w)&&(m.outro("Create task cancelled"),process.exit(0));try{bt(n,{taskName:w}),m.log.info(`Updated ${j.cyan(e)} with task name: ${j.cyan(w)}`);}catch(C){m.outro(`Error: Failed to update file: ${C instanceof Error?C.message:String(C)}`),process.exit(1);}continue}}}}i=E,s=I;break}i||(s?L(s):(f?console.error:m.outro)(j.red("Failed to create task")),process.exit(1));let c=i.id,u=`${b.CLI_PUBLIC_TEMPLATES_URL}/server/org/${a.orgId}/project/${a.projectId}/task/${c}`;f&&(console.info(JSON.stringify(i,null,2)),process.exit(0)),m.log.info(`Run this task with ${j.cyan(`npx rightbrain run-task --task ${c}`)} command.`),m.log.info(`View in dashboard:
|
|
422
|
+
${j.cyan(u)}`),m.outro(j.green("Task created successfully!")),process.exit(0);}let t=await m.text({message:"Where would you like to save the task file?",placeholder:Et,defaultValue:Et,validate(n){if(typeof n=="string"&&n.trim()===".")return "File path cannot be empty"}});m.isCancel(t)&&(m.outro("Create task cancelled"),process.exit(0));let r=t||Et,o=Y(r);if(te.existsSync(o)){let n=await m.confirm({message:`File "${r}" already exists. Overwrite?`,initialValue:false});(m.isCancel(n)||!n)&&(m.outro("Create task cancelled"),process.exit(0));}ee.start(),await or(o),ee.stop(),m.outro(`Edit the file, then run ${j.cyan(`npx rightbrain create-task --file ${r}`)} to create the task.`),process.exit(0);}function ar(e){return e instanceof Error?e.message:String(e)}var cr=_();async function Je(e,t){cr.start();let[r,o]=await p(async()=>{let s=await fetch(e);if(!s.ok)throw new Error(`Failed to fetch ${t}: ${s.status} ${s.statusText}`);return s});if(cr.stop(),o||!r){let s=ar(o);throw m.log.error(j.red(`Failed to fetch ${t}: ${s}`)),new Error(`Failed to fetch ${t}: ${s}`)}let[n,i]=await p(async()=>{let s=await r.text();return JSON.parse(s)});if(i||!n){let s=ar(i);throw m.log.error(j.red(`Failed to parse ${t}: ${s}`)),new Error(`Failed to parse ${t}: ${s}`)}return n}var _t=null;async function Xe(e){if(_t)return _t;let t=await Je(`${e}/discovery.json`,"discovery configuration");return _t=t,t}function Qe(e){let t=e.map(n=>({...n,version:"0"})).sort((n,i)=>n.created.localeCompare(i.created)),r=1,o=1;for(let n=0;n<t.length;n++)if(!t[n].test)t[n].version=`${r}`,r++,o=1;else {let i=r===1?1:r-1;t[n].version=`${i}.${o}`,o++;}return t.reverse()}var et=e=>{if(!e||e.active_revisions.length===0)return;let t=e.active_revisions[0].weight??0,r=0;for(let n=1;n<e.active_revisions.length;n++){let i=e.active_revisions?.[n]?.weight;typeof i=="number"&&i>t&&(t=i,r=n);}return e?.revisions?.find(n=>n.id===e.active_revisions[r].task_revision_id)};var Ie=null;async function _o(e){if(Ie&&Ie[e])return Ie[e];let t=`${b.CLI_PUBLIC_TEMPLATES_URL}/${e}.eta`,r=await fetch(t);if(!r.ok)throw new Error(`Failed to fetch template ${e}: ${r.status} ${r.statusText}`);let o=await r.text();return Ie||(Ie={}),Ie[e]=o,o}async function lr(e){let t=new Eta({autoEscape:false,autoTrim:false,cache:true}),r=await _o(e);return t.loadTemplate("@task-types",r),t}var pr={TASK_TYPES:"@task-types"};function Po(e,t="kebab-case"){switch(t){case "camel-case":return `${dr(e)}.ts`;case "kebab-case":return `${Pt(e)}.ts`;case "pascal-case":return `${fr(e)}.ts`;case "snake-case":return `${ur(e)}.ts`}return `${Pt(e)}.ts`}function Ao(e){let t=e.match(/^\$\{([^}]+)\}$/);return t?t[1].trim():null}function $o(e){let t={};if(e&&typeof e=="object"&&"generate"in e&&e.generate&&typeof e.generate=="object"&&"taskIds"in e.generate&&Array.isArray(e.generate.taskIds)){for(let r of e.generate.taskIds)if(typeof r=="string"){let o=Ao(r);if(o){let n=process.env[o];n&&(t[n]=o);}}}return t}function jo(e){return e&&typeof e=="object"&&"generate"in e&&e.generate&&typeof e.generate=="object"&&"hardcodeTaskIds"in e.generate?e.generate.hardcodeTaskIds===true:false}function Mo(e,t){let r=Qe(e.revisions||[]),o={...e,revisions:r},n=et({...o,revisions:r});if(!n)return null;let i=t?jo(t):false,a=(t&&!i?$o(t):{})[o.id]||null;return {task:o,activeRevision:n,taskIdEnvVar:a,utils:{camelCase:dr,kebabCase:Pt,pascalCase:fr,snakeCase:ur}}}var tt=class{constructor(t,r){this.language=t;this.languageConfig=r;}eta=null;initialized=false;async initialize(){this.initialized||(this.eta=await lr(this.language),this.initialized=true);}generate(t,r){if(!this.eta)throw new Error("TypeGenerator not initialized. Call initialize() first.");let o=Mo(t,r);if(!o)return null;let n=this.eta.render(pr.TASK_TYPES,o);return {fileName:Po(t.name,this.languageConfig.filenameCase),content:n}}generateAll(t,r){let o=[];for(let n of t){let i=this.generate(n,r);i&&o.push(i);}return o}};function Le(e){return e instanceof Error?e.message:String(e)}async function ot(e){let{init:t=false}=e||{},r=Nt(),o=r.generate?.outputDir;o||(m.cancel(`Error: No generate.outputDir found in ${r.filePath}`),process.exit(1)),o=X.join(o);let n=r.generate?.taskIds||[];n.length===0&&(m.cancel(`Error: No generate.taskIds found in ${r.filePath}`),process.exit(1));let i=r.generate?.language;i||(m.cancel(`Error: No generate.language found in ${r.filePath}`),process.exit(1));let s=X.dirname(r.filePath),a=X.resolve(s,o),[c,u]=await p(()=>Xe(b.CLI_PUBLIC_TEMPLATES_URL));(u||!c)&&(m.cancel(`Failed to fetch discovery: ${Le(u)}`),process.exit(1));let l=c[i];l||(m.cancel(`Language ${i} is not currently supported.`),process.exit(1));let d=m.spinner();d.start("Generating types");let v=new tt(i,l);await v.initialize();let E=await T.fromConfig(),I=new Set(n),h=[];for(let P of I){let[q,ae]=await p(()=>E.getTask(P));if(ae){d.stop("Something went wrong"),m.log.warn(j.yellow(`Failed to fetch task ${P}: ${Le(ae)}`)),d.start("Generating types");continue}q&&h.push(q);}h.length===0&&!t&&(m.cancel("No valid tasks to generate."),process.exit(0));let[U,w]=p(()=>v.generateAll(h,r.rawConfig));U||(d.stop("Something went wrong"),m.cancel(`Failed to generate types: ${Le(w)}`),process.exit(1));let[,C]=await p(()=>te.promises.mkdir(a,{recursive:!0}));C&&(d.stop("Something went wrong"),m.cancel(`Failed to create output directory: ${Le(C)}`),process.exit(1));let S=[];for(let{fileName:P,content:q}of U){let ae=X.join(a,P),[,ce]=await p(()=>te.promises.writeFile(ae,q,"utf-8"));if(ce){m.log.warn(j.yellow(`Failed to write ${P}: ${Le(ce)}`));continue}S.push(P);}d.stop(`Generated ${j.cyan(S.length.toString())} file(s) at ${j.green(a)}`),t||m.outro(j.green("Done!"));}function gr(e){e.description("Generate task interface for your RightBrain tasks").action(ot);}var hr=_();function yr(e){return te.existsSync(e)&&te.statSync(e).isFile()}function kr(e){return te.existsSync(e)&&te.statSync(e).isDirectory()}function be(e,t,r){if("all"in e)return e.all.every(o=>be(o,t,r));if("any"in e)return e.any.some(o=>be(o,t,r));if("fileExists"in e)return yr(X.join(t,e.fileExists));if("fileNotExists"in e)return !yr(X.join(t,e.fileNotExists));if("dirExists"in e)return kr(X.join(t,e.dirExists));if("dirNotExists"in e)return !kr(X.join(t,e.dirNotExists));if("fileContains"in e){let o=X.join(t,e.fileContains);if(!te.existsSync(o))return false;try{let n=te.readFileSync(o,"utf-8");return new RegExp(e.pattern,e.flags||"").test(n)}catch{return false}}return "environmentExists"in e?r.has(e.environmentExists):"environmentNotExists"in e?!r.has(e.environmentNotExists):false}function xr(e,t){for(let[r,o]of Object.entries(e))if(o.require){let n=new Set;if(be(o.require,t,n))return r}return null}async function nt(e,t){let r=Object.keys(e);if(r.length===0)throw m.log.error(j.red("No languages available in discovery configuration.")),new Error("No languages available");let o=await m.select({message:"Select a language for your project",options:r.map(s=>({value:s,label:s}))});m.isCancel(o)&&(m.outro("Init cancelled."),process.exit(0));let n=o,i=e[n];if(i.require){let s=new Set;if(!be(i.require,t,s)){let c=i.errorMessage||`Your project does not meet the requirements for ${n}.`;m.log.warn(j.yellow(c));let u=await m.confirm({message:"Do you want to continue anyway?",initialValue:false});if(m.isCancel(u)||!u)return nt(e,t)}}return n}async function So(e,t){let r=await Je(`${e}/${t}.json`,`${t} template`),o={};if(r.environments)for(let[n,i]of Object.entries(r.environments))o[n]={require:i.require,files:[]};return {files:r.files||[],environments:o}}async function Lo(e,t,r){let o=`${e}/${t}-${r}.json`;hr.start();let[n,i]=await p(async()=>{let c=await fetch(o);if(!c.ok){if(c.status===404)return null;throw new Error(`Failed to fetch environment template: ${c.status} ${c.statusText}`)}return c});if(hr.stop(),i||!n)return i&&m.log.warn(j.yellow(`Failed to fetch ${t}-${r} template: ${Be(i)}`)),[];let[s,a]=await p(async()=>{let c=await n.text();return JSON.parse(c)});return a||!s?(m.log.warn(j.yellow(`Failed to parse ${t}-${r} template: ${Be(a)}`)),[]):s.files||[]}function At(e,t){return "environmentNotExists"in e?e.environmentNotExists===t:"all"in e||"any"in e?("all"in e?e.all:e.any).some(o=>At(o,t)):false}function $t(e){return "environmentNotExists"in e?true:"all"in e||"any"in e?("all"in e?e.all:e.any).some(r=>$t(r)):false}function Fo(e,t,r){let o=[],n=new Set(r);for(let[c,u]of Object.entries(e.environments))be(u.require,t,n)&&o.push(c);let i=o.sort((c,u)=>{let l=$t(e.environments[c].require),d=$t(e.environments[u].require);return l&&!d?1:!l&&d?-1:0}),s=[],a=new Set(r);for(let c of i){let u=e.environments[c];if(be(u.require,t,a)&&!s.some(d=>At(e.environments[d].require,c))){for(let d=s.length-1;d>=0;d--){let v=s[d];At(u.require,v)&&(s.splice(d,1),a.delete(v));}s.push(c),a.add(c),r.add(c);}}return s}function wr(e,t){for(let r of e){let o=Ze(r.path,t),n=X.join(t,o),i=X.dirname(n);te.existsSync(i)||te.mkdirSync(i,{recursive:true});try{te.writeFileSync(n,r.content,"utf-8");}catch(s){m.log.warn(j.yellow(`Failed to write ${o}: ${Be(s)}`));}}}async function vr(e,t,r){let o=await So(r,e),n=new Set,i=0;if(o.files.length>0&&(wr(o.files,t),i+=o.files.length),Object.keys(o.environments).length>0){let s=Fo(o,t,n);for(let a of s){let c=await Lo(r,e,a);c.length>0?(wr(c,t),i+=c.length):m.log.warn(j.yellow(`Environment ${a} matched but has no files to apply`));}}i>0&&m.log.success(`Written ${j.cyan(i.toString())} file(s) to quickly get started!`);}var st="rightbrain.json",Ir=".env";function br(e){e.description("Initialize RightBrain configuration for your project").action(Vo);}var Ee=_();async function Uo(e){let t=X.join(e,st);if(te.existsSync(t)){m.log.warn(j.yellow(`A ${st} file already exists at ${e}.`));let r=await m.confirm({message:"Do you want to override the existing configuration?",initialValue:false});(m.isCancel(r)||!r)&&(m.outro("Init cancelled."),process.exit(0));}}async function Bo(e){let[t]=p(()=>(execSync("git rev-parse --is-inside-work-tree",{cwd:e,encoding:"utf-8",stdio:"pipe"}),!0));if(t){let[,r]=p(()=>execSync("git diff --quiet",{cwd:e,encoding:"utf-8"})),o=!!r,[n,i]=p(()=>execSync("git status --porcelain",{cwd:e,encoding:"utf-8"})),s=!i&&n&&n.split(`
|
|
423
|
+
`).some(a=>a.trim().startsWith("??"));if(o||s){let a=o&&s?"You have unstaged changes and untracked files in your repository.":o?"You have unstaged changes in your repository.":"You have untracked files in your repository.";m.log.warn(j.yellow(a));let c=await m.confirm({message:"Do you want to continue anyway?",initialValue:false});(m.isCancel(c)||!c)&&(m.outro("Init cancelled. Please stage or stash your changes first."),process.exit(0));}}else m.log.warn(j.yellow(`No git repository detected.
|
|
424
|
+
Files generated in the next step will not be tracked.`));}async function Oo(){let e,t;try{let r=await Xe(b.CLI_PUBLIC_TEMPLATES_URL),o=xr(r,process.cwd());if(o){let i=await m.confirm({message:`Detected ${j.cyan(o)}, do you want to proceed with it?`,initialValue:!0});m.isCancel(i)&&(m.outro("Init cancelled."),process.exit(0)),i?e=o:e=await nt(r,process.cwd());}else e=await nt(r,process.cwd());t=r[e]?.outputDir||"./lib/rightbrain";}catch(r){let o=r instanceof Error?r.message:String(r);m.log.error(j.red(`Language detection failed: ${o}`)),process.exit(1);}return {selectedLanguage:e,outputDir:Ze(t,process.cwd())}}async function No(){let e=await W({loadGlobalConfig:true});return {selectedProject:await de({accessToken:e.accessToken}),context:e}}async function Cr(e,t,r){let o=await m.text({message:"Enter a name for the new API key",placeholder:"my-api-key",validate:s=>{if(!s||s.trim().length===0)return "API key name is required"}});m.isCancel(o)&&(m.outro("Init cancelled."),process.exit(0)),Ee.start();let[n,i]=await p(async()=>(await e.createApiKey(t,r,{name:o})).data);return Ee.stop(),i||!n?(m.log.error(j.red("Failed to create API key.")),null):(m.log.success(`Created API key: ${j.cyan(n.name)}`),{id:n.id,name:n.name,key:n.key})}async function zo({selectedProject:e}){let t=await W({loadGlobalConfig:true});Ee.start();let r=new APIKeysApi(O(t.accessToken)),[o,n]=await p(async()=>(await r.listApiKeys(e.orgId,e.projectId)).data);Ee.stop();let i=null,s=false;n&&(m.log.warn(j.yellow(`Unable to fetch API keys.
|
|
425
|
+
Visit dashboard to create or retrieve your API key.`)),s=true);let a=(o||[]).filter(c=>!c.revoked);if(a.length>0){let c="__create_new__",u=await m.autocomplete({message:"Select an API key to use",options:[...a.map(l=>({value:l.id,label:l.name,hint:`Created: ${new Date(l.created).toLocaleDateString()}`})),{value:c,label:"Create a new API key",hint:"Generate a new API key for this project"}]});if(m.isCancel(u)&&(m.outro("Init cancelled."),process.exit(0)),u===c)i=await Cr(r,e.orgId,e.projectId);else {let l=a.find(d=>d.id===u);l&&(i={id:l.id,name:l.name,key:l.key});}}else i=await Cr(r,e.orgId,e.projectId);return {selectedApiKey:i,apiKeysFailed:s}}async function qo({wd:e,configFilePath:t,selectedApiKey:r,selectedProject:o,language:n,outputDir:i}){let s=X.join(e,i);te.existsSync(s)||te.mkdirSync(s,{recursive:true});let a={$schema:b.CLI_PUBLIC_TEMPLATES_URL+"/schema.json",orgId:"${RB_ORG_ID}",projectId:"${RB_PROJECT_ID}",apiKey:"${RB_API_KEY}",generate:{language:n,outputDir:i,taskIds:[]}};te.writeFileSync(t,JSON.stringify(a,null,2)+`
|
|
426
|
+
`,"utf-8");let c=X.join(e,Ir),u=r?.key||"<YOUR_API_KEY>",l=`# RightBrain Configuration
|
|
427
|
+
RB_ORG_ID="${o.orgId}"
|
|
428
|
+
RB_PROJECT_ID="${o.projectId}"
|
|
429
|
+
RB_API_KEY="${u}"
|
|
430
|
+
`,d=false;if(te.existsSync(c)){let E=te.readFileSync(c,"utf-8").endsWith(`
|
|
431
|
+
`)?"":`
|
|
432
|
+
`;te.appendFileSync(c,`${E}${l}`,"utf-8"),d=true;}else te.writeFileSync(c,l,"utf-8");return {isEnvFileUpdated:d,configContent:a}}async function Ko({accessToken:e,orgId:t,projectId:r,config:o,configFilePath:n,outputDir:i,language:s}){let a=new TasksApi(O(e));Ee.start();let[c,u]=await p(async()=>(await a.listTasks(t,r)).data.results);if(Ee.stop(),u)return m.log.warn(j.yellow(`Unable to fetch tasks.
|
|
433
|
+
You can manually add task IDs to your config later.`)),false;let l=c||[];if(l.length===0)return m.log.info("No tasks found in this project. You can add task IDs later."),false;let d=await m.autocompleteMultiselect({message:"Select tasks for type generation (type to filter, tab to select)",options:l.map(h=>({value:h.id,label:h.name,hint:h.description?h.description.slice(0,50)+(h.description.length>50?"...":""):void 0})),required:false});if(m.isCancel(d)||d.length===0)return m.log.info("No tasks selected. You can add task IDs later."),false;let v=new Map(l.map(h=>[h.id,h.name])),E=d.map(h=>` /* ${v.get(h)} */ "${h}"`).join(`,
|
|
434
|
+
`),I=`{
|
|
435
|
+
"$schema": "${o.$schema}",
|
|
436
|
+
"orgId": "${o.orgId}",
|
|
437
|
+
"projectId": "${o.projectId}",
|
|
438
|
+
"apiKey": "${o.apiKey}",
|
|
2641
439
|
"generate": {
|
|
2642
|
-
"language": "${
|
|
2643
|
-
"outputDir": "${
|
|
440
|
+
"language": "${s}",
|
|
441
|
+
"outputDir": "${i}",
|
|
2644
442
|
"taskIds": [
|
|
2645
|
-
${
|
|
443
|
+
${E}
|
|
2646
444
|
]
|
|
2647
445
|
}
|
|
2648
446
|
}
|
|
2649
|
-
`;
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
return true;
|
|
2653
|
-
}
|
|
2654
|
-
async function runInit() {
|
|
2655
|
-
await warnIfConfigFileExists(process.cwd());
|
|
2656
|
-
await warnIfGitRepoHasUnstagedChanges(process.cwd());
|
|
2657
|
-
const { selectedLanguage, outputDir } = await detectLanguage();
|
|
2658
|
-
const { selectedProject, context } = await selectProjectInitProject();
|
|
2659
|
-
const { selectedApiKey, apiKeysFailed } = await selectApiKey({ selectedProject });
|
|
2660
|
-
if (!selectedApiKey && !apiKeysFailed) {
|
|
2661
|
-
p11.cancel(`Error: Failed to select or create an API key.`);
|
|
2662
|
-
process.exit(1);
|
|
2663
|
-
}
|
|
2664
|
-
const configFilePath = path7.join(process.cwd(), CONFIG_FILE_NAME2);
|
|
2665
|
-
const { isEnvFileUpdated, configContent } = await generateConfig({
|
|
2666
|
-
configFilePath,
|
|
2667
|
-
selectedApiKey,
|
|
2668
|
-
selectedProject,
|
|
2669
|
-
wd: process.cwd(),
|
|
2670
|
-
language: selectedLanguage,
|
|
2671
|
-
outputDir
|
|
2672
|
-
});
|
|
2673
|
-
p11.log.success(`Generated ${pc8.cyan(CONFIG_FILE_NAME2)}, and ${isEnvFileUpdated ? "updated " : ""}${pc8.cyan(ENV_FILE)}`);
|
|
2674
|
-
try {
|
|
2675
|
-
await applyTemplates(selectedLanguage, process.cwd(), buildEnv.CLI_PUBLIC_TEMPLATES_URL);
|
|
2676
|
-
} catch (error) {
|
|
2677
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2678
|
-
p11.log.error(pc8.red(`Failed to apply templates: ${errorMessage}`));
|
|
2679
|
-
process.exit(1);
|
|
2680
|
-
}
|
|
2681
|
-
const isTasksSelected = await selectTasks({
|
|
2682
|
-
accessToken: context.accessToken,
|
|
2683
|
-
orgId: selectedProject.orgId,
|
|
2684
|
-
projectId: selectedProject.projectId,
|
|
2685
|
-
configFilePath,
|
|
2686
|
-
outputDir,
|
|
2687
|
-
config: configContent
|
|
2688
|
-
});
|
|
2689
|
-
if (isTasksSelected) {
|
|
2690
|
-
await runGenerateCommand({ init: true });
|
|
2691
|
-
}
|
|
2692
|
-
p11.outro(pc8.green("RightBrain project initialized successfully!"));
|
|
2693
|
-
}
|
|
2694
|
-
|
|
2695
|
-
// src/commands/list-models/index.ts
|
|
2696
|
-
import * as p12 from "@clack/prompts";
|
|
2697
|
-
import pc9 from "picocolors";
|
|
2698
|
-
function registerListModelsCommand(program) {
|
|
2699
|
-
program.description("List available LLM models").option("-p, --provider <name>", "Filter by provider (e.g., openai, anthropic)").option("--json", "Output as JSON").option("-c, --compact", "Compact output (ID and name only)").action(listModels);
|
|
2700
|
-
}
|
|
2701
|
-
var spinner8 = getSpinner();
|
|
2702
|
-
function formatContextWindow(tokens) {
|
|
2703
|
-
if (tokens >= 1e6) {
|
|
2704
|
-
return `${(tokens / 1e6).toFixed(1)}M`;
|
|
2705
|
-
}
|
|
2706
|
-
if (tokens >= 1e3) {
|
|
2707
|
-
return `${Math.round(tokens / 1e3)}K`;
|
|
2708
|
-
}
|
|
2709
|
-
return tokens.toString();
|
|
2710
|
-
}
|
|
2711
|
-
async function listModels(options) {
|
|
2712
|
-
spinner8.start();
|
|
2713
|
-
const client = await ApiClient.fromAuthContext();
|
|
2714
|
-
spinner8.stop();
|
|
2715
|
-
spinner8.start();
|
|
2716
|
-
const [models, modelsError] = await tryCatch(() => client.listModels());
|
|
2717
|
-
spinner8.stop();
|
|
2718
|
-
if (modelsError || !models) {
|
|
2719
|
-
handleApiError(modelsError || new Error("Failed to fetch models"));
|
|
2720
|
-
process.exit(1);
|
|
2721
|
-
}
|
|
2722
|
-
const modelList = models;
|
|
2723
|
-
if (modelList.length === 0) {
|
|
2724
|
-
;
|
|
2725
|
-
(isPiped ? console.error : p12.outro)(pc9.red("No models available"));
|
|
2726
|
-
process.exit(0);
|
|
2727
|
-
}
|
|
2728
|
-
let filteredModels = modelList.filter((m) => !m.replaced_by);
|
|
2729
|
-
if (options.provider) {
|
|
2730
|
-
const providerFilter = options.provider.toLowerCase();
|
|
2731
|
-
filteredModels = filteredModels.filter(
|
|
2732
|
-
(m) => m.provider.toLowerCase().includes(providerFilter) || m.vendor.toLowerCase().includes(providerFilter)
|
|
2733
|
-
);
|
|
2734
|
-
if (filteredModels.length === 0) {
|
|
2735
|
-
;
|
|
2736
|
-
(isPiped ? console.error : p12.outro)(pc9.red(`No models found for provider: ${options.provider}`));
|
|
2737
|
-
process.exit(0);
|
|
2738
|
-
}
|
|
2739
|
-
}
|
|
2740
|
-
if (options.json || isPiped) {
|
|
2741
|
-
console.info(JSON.stringify(filteredModels, null, 2));
|
|
2742
|
-
process.exit(0);
|
|
2743
|
-
}
|
|
2744
|
-
const grouped = groupModelsByProvider(filteredModels);
|
|
2745
|
-
const sortedProviders = Array.from(grouped.keys()).sort();
|
|
2746
|
-
p12.log.info(pc9.bold(`Available Models (${filteredModels.length} total)`));
|
|
2747
|
-
console.info();
|
|
2748
|
-
if (options.compact) {
|
|
2749
|
-
for (const provider of sortedProviders) {
|
|
2750
|
-
const providerModels = grouped.get(provider);
|
|
2751
|
-
console.info(pc9.bold(pc9.cyan(`\u25B8 ${provider}`)));
|
|
2752
|
-
for (const model of providerModels) {
|
|
2753
|
-
console.info(` ${model.alias}`);
|
|
2754
|
-
console.info(` ${pc9.dim(model.id)}`);
|
|
2755
|
-
}
|
|
2756
|
-
console.info();
|
|
2757
|
-
}
|
|
2758
|
-
} else {
|
|
2759
|
-
for (const provider of sortedProviders) {
|
|
2760
|
-
const providerModels = grouped.get(provider);
|
|
2761
|
-
console.info(pc9.bold(pc9.cyan(`\u25B8 ${provider}`)) + pc9.dim(` (${providerModels.length} models)`));
|
|
2762
|
-
console.info();
|
|
2763
|
-
for (const model of providerModels) {
|
|
2764
|
-
console.info(` ${pc9.bold(model.alias)}`);
|
|
2765
|
-
console.info(` ${pc9.dim("ID:")} ${pc9.green(model.id)}`);
|
|
2766
|
-
const info = [];
|
|
2767
|
-
if (model.vendor !== model.provider) {
|
|
2768
|
-
info.push(`via ${model.vendor}`);
|
|
2769
|
-
}
|
|
2770
|
-
if (model.description) {
|
|
2771
|
-
info.push(model.description);
|
|
2772
|
-
}
|
|
2773
|
-
if (model.supports_vision) {
|
|
2774
|
-
info.push(pc9.magenta("\u{1F441} vision"));
|
|
2775
|
-
}
|
|
2776
|
-
if (model.max_context_window) {
|
|
2777
|
-
info.push(`${formatContextWindow(model.max_context_window)} ctx`);
|
|
2778
|
-
}
|
|
2779
|
-
if (info.length > 0) {
|
|
2780
|
-
console.info(` ${pc9.dim(info.join(" \xB7 "))}`);
|
|
2781
|
-
}
|
|
2782
|
-
console.info();
|
|
2783
|
-
}
|
|
2784
|
-
}
|
|
2785
|
-
}
|
|
2786
|
-
if (!options.provider || !options.compact) {
|
|
2787
|
-
console.info(pc9.dim("\u2500".repeat(40)));
|
|
2788
|
-
}
|
|
2789
|
-
if (!options.provider) {
|
|
2790
|
-
console.info(pc9.dim("Use --provider <name> to filter by provider."));
|
|
2791
|
-
}
|
|
2792
|
-
if (!options.compact) {
|
|
2793
|
-
console.info(pc9.dim("Use --compact for a condensed view."));
|
|
2794
|
-
}
|
|
2795
|
-
}
|
|
2796
|
-
|
|
2797
|
-
// src/commands/login/index.ts
|
|
2798
|
-
import * as p13 from "@clack/prompts";
|
|
2799
|
-
import { UsersApi } from "@rightbrain/brain-api-client";
|
|
2800
|
-
import open2 from "open";
|
|
2801
|
-
import pc10 from "picocolors";
|
|
2802
|
-
function registerLoginCommand(program) {
|
|
2803
|
-
program.description("Authenticate with RightBrain").action(runLogin);
|
|
2804
|
-
}
|
|
2805
|
-
async function runLogin() {
|
|
2806
|
-
const existingCredentials = loadCredentials();
|
|
2807
|
-
if (existingCredentials) {
|
|
2808
|
-
const [user] = await tryCatch(async () => {
|
|
2809
|
-
const usersApi = new UsersApi(getConfiguration(existingCredentials.access_token));
|
|
2810
|
-
return await usersApi.getCurrentUser().then((response) => response.data);
|
|
2811
|
-
});
|
|
2812
|
-
if (user) {
|
|
2813
|
-
const overwrite = await p13.confirm({
|
|
2814
|
-
message: `You are already logged in as ${pc10.cyan(user.email)}. Do you want to login again?`,
|
|
2815
|
-
initialValue: false
|
|
2816
|
-
});
|
|
2817
|
-
if (p13.isCancel(overwrite) || !overwrite) {
|
|
2818
|
-
p13.outro("Login cancelled");
|
|
2819
|
-
process.exit(0);
|
|
2820
|
-
}
|
|
2821
|
-
}
|
|
2822
|
-
}
|
|
2823
|
-
const codeVerifier = generateCodeVerifier();
|
|
2824
|
-
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
|
2825
|
-
const state = generateState();
|
|
2826
|
-
const authUrl = getAuthorizationUrl(state, codeChallenge);
|
|
2827
|
-
const loginUrl = getLoginUrl();
|
|
2828
|
-
const serverPromise = startCallbackServer(state, codeVerifier, authUrl);
|
|
2829
|
-
const spinner13 = p13.spinner();
|
|
2830
|
-
spinner13.start("Press Enter to open browser for authentication...");
|
|
2831
|
-
const enterPromise = new Promise((resolve) => {
|
|
2832
|
-
const cleanup = waitForEnter(resolve);
|
|
2833
|
-
serverPromise.finally(cleanup);
|
|
2834
|
-
});
|
|
2835
|
-
const raceResult = await Promise.race([
|
|
2836
|
-
enterPromise.then(() => ({ type: "enter" })),
|
|
2837
|
-
serverPromise.then(() => ({ type: "auth" }))
|
|
2838
|
-
]);
|
|
2839
|
-
if (raceResult.type === "enter") {
|
|
2840
|
-
spinner13.message("Opening browser for authentication...");
|
|
2841
|
-
const [, openError] = await tryCatch(() => open2(loginUrl));
|
|
2842
|
-
if (openError) {
|
|
2843
|
-
p13.log.warn(`Could not open browser automatically. Please open the URL manually.
|
|
2844
|
-
${pc10.cyan(loginUrl)}`);
|
|
2845
|
-
}
|
|
2846
|
-
}
|
|
2847
|
-
const result = await serverPromise;
|
|
2848
|
-
if (!result.success || !result.token) {
|
|
2849
|
-
spinner13.stop("Authentication failed");
|
|
2850
|
-
p13.cancel(`Error: Authentication failed: ${result.error || "Unknown error"}`);
|
|
2851
|
-
process.exit(1);
|
|
2852
|
-
}
|
|
2853
|
-
const expiresAt = result.token.expires_in ? Date.now() + result.token.expires_in * 1e3 : void 0;
|
|
2854
|
-
saveCredentials({
|
|
2855
|
-
access_token: result.token.access_token,
|
|
2856
|
-
refresh_token: result.token.refresh_token,
|
|
2857
|
-
expires_at: expiresAt,
|
|
2858
|
-
org_id: void 0,
|
|
2859
|
-
project_id: void 0
|
|
2860
|
-
});
|
|
2861
|
-
spinner13.stop(pc10.green("You're now logged in!"));
|
|
2862
|
-
const selectedProject = await selectProject({ accessToken: result.token.access_token });
|
|
2863
|
-
saveCredentials({
|
|
2864
|
-
org_id: selectedProject.orgId,
|
|
2865
|
-
project_id: selectedProject.projectId
|
|
2866
|
-
});
|
|
2867
|
-
p13.outro(pc10.green(`Selected organization and project`));
|
|
2868
|
-
}
|
|
2869
|
-
|
|
2870
|
-
// src/commands/logout/index.ts
|
|
2871
|
-
import * as p14 from "@clack/prompts";
|
|
2872
|
-
import { UsersApi as UsersApi2 } from "@rightbrain/brain-api-client";
|
|
2873
|
-
import pc11 from "picocolors";
|
|
2874
|
-
function registerLogoutCommand(program) {
|
|
2875
|
-
program.description("Log out from RightBrain").option("-y, --yes", "Skip confirmation prompt").action(runLogout);
|
|
2876
|
-
}
|
|
2877
|
-
async function runLogout(options) {
|
|
2878
|
-
const credentials2 = loadCredentials();
|
|
2879
|
-
if (!credentials2) {
|
|
2880
|
-
p14.log.info("You are not logged in.");
|
|
2881
|
-
return;
|
|
2882
|
-
}
|
|
2883
|
-
if (!options.yes) {
|
|
2884
|
-
const [user] = await tryCatch(async () => {
|
|
2885
|
-
const usersApi = new UsersApi2(getConfiguration(credentials2.access_token));
|
|
2886
|
-
return await usersApi.getCurrentUser().then((response) => response.data);
|
|
2887
|
-
});
|
|
2888
|
-
if (user) {
|
|
2889
|
-
const confirm6 = await p14.confirm({
|
|
2890
|
-
message: "Are you sure you want to log out?",
|
|
2891
|
-
initialValue: false
|
|
2892
|
-
});
|
|
2893
|
-
if (p14.isCancel(confirm6) || !confirm6) {
|
|
2894
|
-
p14.outro("Logout cancelled");
|
|
2895
|
-
process.exit(0);
|
|
2896
|
-
}
|
|
2897
|
-
}
|
|
2898
|
-
}
|
|
2899
|
-
clearCredentials();
|
|
2900
|
-
p14.log.success(pc11.green("Successfully logged out!"));
|
|
2901
|
-
}
|
|
2902
|
-
|
|
2903
|
-
// src/commands/run-task/input-processor.ts
|
|
2904
|
-
import * as p15 from "@clack/prompts";
|
|
2905
|
-
import { fileTypeFromBuffer } from "file-type";
|
|
2906
|
-
import fs9 from "fs";
|
|
2907
|
-
import pc12 from "picocolors";
|
|
2908
|
-
import z3 from "zod";
|
|
2909
|
-
var urlSchema = z3.url();
|
|
2910
|
-
var textInput = async (ip, { providedInputNames, inputs, providedInputs }) => {
|
|
2911
|
-
const indexOfProvidedInput = providedInputNames.indexOf(ip.param_name);
|
|
2912
|
-
if (indexOfProvidedInput !== -1) {
|
|
2913
|
-
inputs[ip.param_name] = providedInputs[ip.param_name];
|
|
2914
|
-
p15.log.info(`${ip.param_name}: ${pc12.cyan(providedInputs[ip.param_name])}`);
|
|
2915
|
-
return;
|
|
2916
|
-
}
|
|
2917
|
-
const value = await p15.text({
|
|
2918
|
-
message: `${ip.param_name} (${ip.input_processor === "perplexity_search" ? "Search query Perplexity" : "Text"}):`,
|
|
2919
|
-
placeholder: ip.input_processor === "perplexity_search" ? "Enter search query" : "Enter value",
|
|
2920
|
-
validate: (value2) => {
|
|
2921
|
-
if (!value2) return `${ip.param_name} is required`;
|
|
2922
|
-
return void 0;
|
|
2923
|
-
}
|
|
2924
|
-
});
|
|
2925
|
-
if (p15.isCancel(value)) {
|
|
2926
|
-
p15.outro("Run task cancelled");
|
|
2927
|
-
process.exit(0);
|
|
2928
|
-
}
|
|
2929
|
-
inputs[ip.param_name] = value;
|
|
2930
|
-
};
|
|
2931
|
-
var inputProcessorValues = {
|
|
2932
|
-
"": textInput,
|
|
2933
|
-
perplexity_search: textInput,
|
|
2934
|
-
url_fetcher: async (ip, { providedInputNames, inputs, providedInputs }) => {
|
|
2935
|
-
const indexOfProvidedInput = providedInputNames.indexOf(ip.param_name);
|
|
2936
|
-
if (indexOfProvidedInput !== -1) {
|
|
2937
|
-
inputs[ip.param_name] = providedInputs[ip.param_name];
|
|
2938
|
-
p15.log.info(`${ip.param_name}: ${pc12.cyan(providedInputs[ip.param_name])}`);
|
|
2939
|
-
return;
|
|
2940
|
-
}
|
|
2941
|
-
const url = await p15.text({
|
|
2942
|
-
message: `${ip.param_name} (Web crawler URL):`,
|
|
2943
|
-
placeholder: "https://example.com",
|
|
2944
|
-
validate: (value) => {
|
|
2945
|
-
if (!value) return "URL is required";
|
|
2946
|
-
if (!urlSchema.safeParse(value).success) return "Invalid URL format";
|
|
2947
|
-
return void 0;
|
|
2948
|
-
}
|
|
2949
|
-
});
|
|
2950
|
-
if (p15.isCancel(url)) {
|
|
2951
|
-
p15.outro("Run task cancelled");
|
|
2952
|
-
process.exit(0);
|
|
2953
|
-
}
|
|
2954
|
-
inputs[ip.param_name] = url;
|
|
2955
|
-
},
|
|
2956
|
-
document_content_extractor: async (ip, { files, providedFileInputNames, providedFiles }) => {
|
|
2957
|
-
if (!ip.config || !("filename" in ip.config) || typeof ip.config.filename !== "string") {
|
|
2958
|
-
p15.outro(pc12.red(`ERROR: Unable to find input processor for ${ip.param_name}`));
|
|
2959
|
-
process.exit(1);
|
|
2960
|
-
}
|
|
2961
|
-
const filename = ip.config.filename;
|
|
2962
|
-
const indexOfProvidedFileInput = providedFileInputNames.indexOf(filename);
|
|
2963
|
-
if (indexOfProvidedFileInput !== -1) {
|
|
2964
|
-
files.push(providedFiles[indexOfProvidedFileInput]);
|
|
2965
|
-
p15.log.info(`${ip.param_name}: ${pc12.cyan(filename)}`);
|
|
2966
|
-
return;
|
|
2967
|
-
}
|
|
2968
|
-
const filePath = await p15.text({
|
|
2969
|
-
message: `${ip.param_name} (Document file):`,
|
|
2970
|
-
placeholder: "Enter file path",
|
|
2971
|
-
validate: (value) => {
|
|
2972
|
-
if (!value) return "File path is required";
|
|
2973
|
-
const resolved = resolvePath(value);
|
|
2974
|
-
if (!fs9.existsSync(resolved)) return "File not found";
|
|
2975
|
-
return void 0;
|
|
2976
|
-
}
|
|
2977
|
-
});
|
|
2978
|
-
if (p15.isCancel(filePath)) {
|
|
2979
|
-
p15.outro("Run task cancelled");
|
|
2980
|
-
process.exit(0);
|
|
2981
|
-
}
|
|
2982
|
-
const resolvedPath = resolvePath(filePath);
|
|
2983
|
-
const fileBuffer = fs9.readFileSync(resolvedPath);
|
|
2984
|
-
const fileType = await fileTypeFromBuffer(fileBuffer);
|
|
2985
|
-
const mimeType = fileType?.mime || "application/octet-stream";
|
|
2986
|
-
files.push(new File([fileBuffer], filename, { type: mimeType }));
|
|
2987
|
-
}
|
|
2988
|
-
};
|
|
2989
|
-
|
|
2990
|
-
// src/commands/run-task/index.ts
|
|
2991
|
-
import * as p16 from "@clack/prompts";
|
|
2992
|
-
import { fileTypeFromBuffer as fileTypeFromBuffer2 } from "file-type";
|
|
2993
|
-
import fs10 from "fs";
|
|
2994
|
-
import path8 from "path";
|
|
2995
|
-
import { inspect as inspect2 } from "util";
|
|
2996
|
-
import pc13 from "picocolors";
|
|
2997
|
-
function parseInputFlag(inputFlag) {
|
|
2998
|
-
const eqIndex = inputFlag.indexOf("=");
|
|
2999
|
-
if (eqIndex === -1) {
|
|
3000
|
-
return null;
|
|
3001
|
-
}
|
|
3002
|
-
return {
|
|
3003
|
-
key: inputFlag.slice(0, eqIndex),
|
|
3004
|
-
value: inputFlag.slice(eqIndex + 1)
|
|
3005
|
-
};
|
|
3006
|
-
}
|
|
3007
|
-
function registerRunTaskCommand(program) {
|
|
3008
|
-
program.description("Run a task").option("-t, --task <id>", "Task ID to run").option("-r, --revision <id>", "Specific revision ID").option(
|
|
3009
|
-
"-i, --input <name=value>",
|
|
3010
|
-
"Input parameter (repeatable)",
|
|
3011
|
-
(value, prev) => {
|
|
3012
|
-
prev.push(value);
|
|
3013
|
-
return prev;
|
|
3014
|
-
},
|
|
3015
|
-
[]
|
|
3016
|
-
).option(
|
|
3017
|
-
"-f, --file <name=path>",
|
|
3018
|
-
"File to upload (repeatable)",
|
|
3019
|
-
(value, prev) => {
|
|
3020
|
-
prev.push(value);
|
|
3021
|
-
return prev;
|
|
3022
|
-
},
|
|
3023
|
-
[]
|
|
3024
|
-
).option(
|
|
3025
|
-
"--image <path>",
|
|
3026
|
-
"Image to attach (repeatable)",
|
|
3027
|
-
(value, prev) => {
|
|
3028
|
-
prev.push(value);
|
|
3029
|
-
return prev;
|
|
3030
|
-
},
|
|
3031
|
-
[]
|
|
3032
|
-
).option(
|
|
3033
|
-
"-d, --direct",
|
|
3034
|
-
"Run the task directly without fetching resources first. Recommended for production environments where inputs and files are already provided."
|
|
3035
|
-
).option("--use-fallback-model", "Use fallback model for the task").action(runTask);
|
|
3036
|
-
}
|
|
3037
|
-
var spinner11 = getSpinner();
|
|
3038
|
-
async function runTask(options) {
|
|
3039
|
-
spinner11.start();
|
|
3040
|
-
const client = await ApiClient.fromAuthContext();
|
|
3041
|
-
spinner11.stop();
|
|
3042
|
-
const request = await (options.direct || isPiped ? runTaskDirectly(options) : runTaskInteractively(options, client));
|
|
3043
|
-
const clackSpinner = p16.spinner();
|
|
3044
|
-
if (isPiped) {
|
|
3045
|
-
spinner11.start();
|
|
3046
|
-
} else {
|
|
3047
|
-
clackSpinner.start("Running task...");
|
|
3048
|
-
}
|
|
3049
|
-
const [result, runError] = await tryCatch(
|
|
3050
|
-
() => client.runTask({ ...request, useFallbackModel: options.useFallbackModel })
|
|
3051
|
-
);
|
|
3052
|
-
spinner11.stop();
|
|
3053
|
-
(isPiped ? console.error : clackSpinner.stop)(runError ? "Failed to run task" : "Task run successfully");
|
|
3054
|
-
if (runError) {
|
|
3055
|
-
handleApiError(runError);
|
|
3056
|
-
process.exit(1);
|
|
3057
|
-
}
|
|
3058
|
-
console.info(isPiped ? JSON.stringify(result, null, 2) : inspect2(result, { depth: null, colors: true }));
|
|
3059
|
-
}
|
|
3060
|
-
function collectInputs(inputFlags, hint = "name=value") {
|
|
3061
|
-
const inputs = {};
|
|
3062
|
-
for (const inputFlag of inputFlags) {
|
|
3063
|
-
const parsed = parseInputFlag(inputFlag);
|
|
3064
|
-
if (!parsed) {
|
|
3065
|
-
;
|
|
3066
|
-
(isPiped ? console.error : p16.outro)(pc13.red(`Invalid input format: ${inputFlag}. Expected ${hint}`));
|
|
3067
|
-
process.exit(1);
|
|
3068
|
-
}
|
|
3069
|
-
inputs[parsed.key] = parsed.value;
|
|
3070
|
-
}
|
|
3071
|
-
return inputs;
|
|
3072
|
-
}
|
|
3073
|
-
async function collectFiles(providedFiles) {
|
|
3074
|
-
const files = await Promise.all(
|
|
3075
|
-
Object.entries(providedFiles).map(async ([name, filePath]) => {
|
|
3076
|
-
const resolvedPath = resolvePath(filePath);
|
|
3077
|
-
if (!fs10.existsSync(resolvedPath)) {
|
|
3078
|
-
;
|
|
3079
|
-
(isPiped ? console.error : p16.outro)(pc13.red(`Error: File not found: ${filePath}`));
|
|
3080
|
-
process.exit(1);
|
|
3081
|
-
}
|
|
3082
|
-
const fileBuffer = fs10.readFileSync(resolvedPath);
|
|
3083
|
-
const fileType = await fileTypeFromBuffer2(fileBuffer);
|
|
3084
|
-
const mimeType = fileType?.mime || "application/octet-stream";
|
|
3085
|
-
return new File([fileBuffer], name, { type: mimeType });
|
|
3086
|
-
})
|
|
3087
|
-
);
|
|
3088
|
-
return files;
|
|
3089
|
-
}
|
|
3090
|
-
async function collectImages(imageFlags) {
|
|
3091
|
-
const images = await Promise.all(
|
|
3092
|
-
imageFlags.map(async (imageFlag) => {
|
|
3093
|
-
const resolvedPath = resolvePath(imageFlag);
|
|
3094
|
-
if (!fs10.existsSync(resolvedPath)) {
|
|
3095
|
-
;
|
|
3096
|
-
(isPiped ? console.error : p16.outro)(pc13.red(`Error: File not found: ${imageFlag}`));
|
|
3097
|
-
process.exit(1);
|
|
3098
|
-
}
|
|
3099
|
-
const imageBuffer = fs10.readFileSync(resolvedPath);
|
|
3100
|
-
const fileType = await fileTypeFromBuffer2(imageBuffer);
|
|
3101
|
-
const mimeType = fileType?.mime || "application/octet-stream";
|
|
3102
|
-
const filename = path8.basename(resolvedPath);
|
|
3103
|
-
return new File([imageBuffer], filename, { type: mimeType });
|
|
3104
|
-
})
|
|
3105
|
-
);
|
|
3106
|
-
return images;
|
|
3107
|
-
}
|
|
3108
|
-
async function runTaskInteractively(options, client) {
|
|
3109
|
-
if (!options.task) {
|
|
3110
|
-
spinner11.start();
|
|
3111
|
-
const [tasksResult, listError] = await tryCatch(() => client.listTasks());
|
|
3112
|
-
spinner11.stop();
|
|
3113
|
-
if (listError) {
|
|
3114
|
-
handleApiError(listError);
|
|
3115
|
-
process.exit(1);
|
|
3116
|
-
}
|
|
3117
|
-
const tasks = tasksResult;
|
|
3118
|
-
if (!tasks || tasks.length === 0) {
|
|
3119
|
-
;
|
|
3120
|
-
(isPiped ? console.error : p16.outro)(pc13.red(`No tasks found in this project.`));
|
|
3121
|
-
(isPiped ? console.error : p16.outro)(`Create a task with \`npx rightbrain create-task\` command.`);
|
|
3122
|
-
process.exit(1);
|
|
3123
|
-
}
|
|
3124
|
-
if (tasks.length === 1) {
|
|
3125
|
-
options.task = tasks[0].id;
|
|
3126
|
-
(isPiped ? console.error : p16.log.info)(`Using task: ${pc13.cyan(tasks[0].name)}`);
|
|
3127
|
-
} else {
|
|
3128
|
-
const selectedTask = await p16.select({
|
|
3129
|
-
message: "Select a task to run",
|
|
3130
|
-
options: tasks.map((t) => ({
|
|
3131
|
-
value: t.id,
|
|
3132
|
-
label: t.name,
|
|
3133
|
-
hint: t.id
|
|
3134
|
-
}))
|
|
3135
|
-
});
|
|
3136
|
-
if (p16.isCancel(selectedTask)) {
|
|
3137
|
-
p16.outro("Run task cancelled");
|
|
3138
|
-
process.exit(0);
|
|
3139
|
-
}
|
|
3140
|
-
options.task = selectedTask;
|
|
3141
|
-
}
|
|
3142
|
-
}
|
|
3143
|
-
spinner11.start();
|
|
3144
|
-
const [fetchedTask, fetchError] = await tryCatch(() => client.getTask(options.task));
|
|
3145
|
-
spinner11.stop();
|
|
3146
|
-
if (fetchError || !fetchedTask) {
|
|
3147
|
-
handleApiError(fetchError || new Error("Task not found"));
|
|
3148
|
-
process.exit(1);
|
|
3149
|
-
}
|
|
3150
|
-
const task = { ...fetchedTask, revisions: revisionsWithVersion(fetchedTask.revisions || []) };
|
|
3151
|
-
let revision = task.revisions.length === 1 ? task.revisions[0] : void 0;
|
|
3152
|
-
if (!revision) {
|
|
3153
|
-
if (options.revision) {
|
|
3154
|
-
revision = task.revisions?.find((r) => r.id === options.revision || r.version === options.revision);
|
|
3155
|
-
if (!revision) {
|
|
3156
|
-
;
|
|
3157
|
-
(isPiped ? console.error : p16.outro)(pc13.red(`Error: Revision ${options.revision} not found`));
|
|
3158
|
-
process.exit(1);
|
|
3159
|
-
}
|
|
3160
|
-
} else {
|
|
3161
|
-
revision = getActiveRevision(task);
|
|
3162
|
-
if (revision) {
|
|
3163
|
-
;
|
|
3164
|
-
(isPiped ? console.error : p16.log.info)(`Using active revision`);
|
|
3165
|
-
} else {
|
|
3166
|
-
;
|
|
3167
|
-
(isPiped ? console.error : p16.outro)(
|
|
3168
|
-
pc13.red("Error: No active revision found for this task. Please specify a revision ID.")
|
|
3169
|
-
);
|
|
3170
|
-
process.exit(1);
|
|
3171
|
-
}
|
|
3172
|
-
}
|
|
3173
|
-
}
|
|
3174
|
-
const inputParams = revision.input_params ?? [];
|
|
3175
|
-
const inputProcessors = revision.input_processors ?? [];
|
|
3176
|
-
const imageRequired = revision.image_required ?? false;
|
|
3177
|
-
const providedInputs = collectInputs(options.input || []);
|
|
3178
|
-
const providedInputNames = Object.keys(providedInputs);
|
|
3179
|
-
const providedInputFileEntity = collectInputs(options.file || [], "name=path");
|
|
3180
|
-
const providedFileInputNames = Object.keys(providedInputFileEntity);
|
|
3181
|
-
const providedFiles = await collectFiles(providedInputFileEntity);
|
|
3182
|
-
const inputs = {};
|
|
3183
|
-
const files = [];
|
|
3184
|
-
for (const paramName of inputParams) {
|
|
3185
|
-
const ip = inputProcessors.find((e) => e.param_name === paramName) ?? {
|
|
3186
|
-
param_name: paramName,
|
|
3187
|
-
input_processor: ""
|
|
3188
|
-
};
|
|
3189
|
-
const cb = inputProcessorValues[ip.input_processor];
|
|
3190
|
-
if (!cb) {
|
|
3191
|
-
;
|
|
3192
|
-
(isPiped ? console.error : p16.outro)(pc13.red(`ERROR: Unable to find input processor for ${ip.param_name}`));
|
|
3193
|
-
process.exit(1);
|
|
3194
|
-
}
|
|
3195
|
-
await cb(ip, {
|
|
3196
|
-
files,
|
|
3197
|
-
inputs,
|
|
3198
|
-
providedFileInputNames,
|
|
3199
|
-
providedFiles,
|
|
3200
|
-
providedInputNames,
|
|
3201
|
-
providedInputs
|
|
3202
|
-
});
|
|
3203
|
-
}
|
|
3204
|
-
const providedImages = await collectImages(options.image || []);
|
|
3205
|
-
if (providedImages.length > 0 && !isPiped) {
|
|
3206
|
-
p16.log.info(
|
|
3207
|
-
`Image${providedImages.length > 1 ? "s" : ""}: ${pc13.cyan(providedImages.map((image) => image.name).join(", "))}`
|
|
3208
|
-
);
|
|
3209
|
-
}
|
|
3210
|
-
if (imageRequired && providedImages.length === 0) {
|
|
3211
|
-
const imagePath = await p16.text({
|
|
3212
|
-
message: "Image required - enter path:",
|
|
3213
|
-
placeholder: "path/to/image.jpg",
|
|
3214
|
-
validate: (value) => {
|
|
3215
|
-
if (!value) return "Path is required";
|
|
3216
|
-
const resolved = resolvePath(value);
|
|
3217
|
-
if (!fs10.existsSync(resolved)) return "File not found";
|
|
3218
|
-
return void 0;
|
|
3219
|
-
}
|
|
3220
|
-
});
|
|
3221
|
-
if (p16.isCancel(imagePath)) {
|
|
3222
|
-
p16.outro("Run task cancelled");
|
|
3223
|
-
process.exit(0);
|
|
3224
|
-
}
|
|
3225
|
-
const resolvedPath = resolvePath(imagePath);
|
|
3226
|
-
const imageBuffer = fs10.readFileSync(resolvedPath);
|
|
3227
|
-
const filename = path8.basename(resolvedPath);
|
|
3228
|
-
const fileType = await fileTypeFromBuffer2(imageBuffer);
|
|
3229
|
-
const mimeType = fileType?.mime || "application/octet-stream";
|
|
3230
|
-
providedImages.push(new File([imageBuffer], filename, { type: mimeType }));
|
|
3231
|
-
}
|
|
3232
|
-
return {
|
|
3233
|
-
taskId: task.id,
|
|
3234
|
-
revisionId: revision.id,
|
|
3235
|
-
body: {
|
|
3236
|
-
task_input: inputs,
|
|
3237
|
-
task_files: [...providedImages, ...files]
|
|
3238
|
-
}
|
|
3239
|
-
};
|
|
3240
|
-
}
|
|
3241
|
-
async function runTaskDirectly(options) {
|
|
3242
|
-
if (!options.task) {
|
|
3243
|
-
;
|
|
3244
|
-
(isPiped ? console.error : p16.outro)(pc13.red("Error: Task ID is required when running directly"));
|
|
3245
|
-
process.exit(1);
|
|
3246
|
-
}
|
|
3247
|
-
if (!isValidUUID(options.task)) {
|
|
3248
|
-
;
|
|
3249
|
-
(isPiped ? console.error : p16.outro)(pc13.red("Error: Task ID must be a valid UUID when running directly"));
|
|
3250
|
-
process.exit(1);
|
|
3251
|
-
}
|
|
3252
|
-
if (options.revision && !isValidUUID(options.revision)) {
|
|
3253
|
-
;
|
|
3254
|
-
(isPiped ? console.error : p16.outro)(pc13.red("Error: Revision must be a valid UUID when running directly"));
|
|
3255
|
-
process.exit(1);
|
|
3256
|
-
}
|
|
3257
|
-
const images = await collectImages(options.image || []);
|
|
3258
|
-
const files = await collectFiles(collectInputs(options.file || [], "name=path"));
|
|
3259
|
-
return {
|
|
3260
|
-
body: {
|
|
3261
|
-
task_input: collectInputs(options.input || []),
|
|
3262
|
-
task_files: [...images, ...files]
|
|
3263
|
-
},
|
|
3264
|
-
taskId: options.task,
|
|
3265
|
-
revisionId: options.revision
|
|
3266
|
-
};
|
|
3267
|
-
}
|
|
3268
|
-
|
|
3269
|
-
// src/commands/switch-project/index.ts
|
|
3270
|
-
import * as p17 from "@clack/prompts";
|
|
3271
|
-
import pc14 from "picocolors";
|
|
3272
|
-
function registerSwitchProjectCommand(program) {
|
|
3273
|
-
program.description("Switch to a different organization and project").option("--org-id <orgId>", "Organization ID to switch to").option("--project-id <projectId>", "Project ID to switch to").action(runSwitchProject);
|
|
3274
|
-
}
|
|
3275
|
-
var spinner12 = getSpinner();
|
|
3276
|
-
async function runSwitchProject(options) {
|
|
3277
|
-
const context = await getAuthContext();
|
|
3278
|
-
if (context.filePath) {
|
|
3279
|
-
p17.log.info(
|
|
3280
|
-
pc14.yellow("\u26A0\uFE0F This command is for updating the global project/organization context used by the CLI.\n") + " If you want to switch projects inside a specific project/config-based setup, please update the orgId and projectId values in your configuration file directly."
|
|
3281
|
-
);
|
|
3282
|
-
}
|
|
3283
|
-
if (options.orgId || options.projectId) {
|
|
3284
|
-
if (!options.orgId) {
|
|
3285
|
-
p17.cancel("Organization ID (--org-id) is required when specifying a project ID (--project-id)");
|
|
3286
|
-
process.exit(1);
|
|
3287
|
-
}
|
|
3288
|
-
if (!options.projectId) {
|
|
3289
|
-
p17.cancel("Project ID (--project-id) is required when specifying an organization ID (--org-id)");
|
|
3290
|
-
process.exit(1);
|
|
3291
|
-
}
|
|
3292
|
-
}
|
|
3293
|
-
let projectSelection;
|
|
3294
|
-
if (options.orgId || options.projectId) {
|
|
3295
|
-
spinner12.start();
|
|
3296
|
-
const orgId2 = options.orgId;
|
|
3297
|
-
const projectId2 = options.projectId;
|
|
3298
|
-
const apiClient = new ApiClient({
|
|
3299
|
-
accessToken: context.accessToken,
|
|
3300
|
-
orgId: orgId2,
|
|
3301
|
-
projectId: projectId2
|
|
3302
|
-
});
|
|
3303
|
-
const [org, orgError] = await tryCatch(() => apiClient.getOrganization(orgId2));
|
|
3304
|
-
if (orgError || !org) {
|
|
3305
|
-
handleApiError(orgError || new Error("Organization not found"));
|
|
3306
|
-
process.exit(1);
|
|
3307
|
-
}
|
|
3308
|
-
spinner12.stop();
|
|
3309
|
-
p17.log.info(`\u2713 Organization access confirmed: ${pc14.cyan(org.name || orgId2)}`);
|
|
3310
|
-
spinner12.start();
|
|
3311
|
-
const [project, projectError] = await tryCatch(() => apiClient.getProject(orgId2, projectId2));
|
|
3312
|
-
if (projectError || !project) {
|
|
3313
|
-
handleApiError(projectError || new Error("Project not found"));
|
|
3314
|
-
process.exit(1);
|
|
3315
|
-
}
|
|
3316
|
-
spinner12.stop();
|
|
3317
|
-
p17.log.info(`\u2713 Project access confirmed: ${pc14.cyan(project.name || projectId2)}`);
|
|
3318
|
-
projectSelection = { orgId: orgId2, projectId: projectId2 };
|
|
3319
|
-
p17.log.success(
|
|
3320
|
-
`Switched to organization: ${pc14.cyan(org.name || orgId2)}, project: ${pc14.cyan(project.name || projectId2)}`
|
|
3321
|
-
);
|
|
3322
|
-
} else {
|
|
3323
|
-
const selectedProject = await selectProject({
|
|
3324
|
-
...await getAuthContext({ loadGlobalConfig: true }),
|
|
3325
|
-
orgId: void 0,
|
|
3326
|
-
projectId: void 0
|
|
3327
|
-
});
|
|
3328
|
-
projectSelection = selectedProject;
|
|
3329
|
-
p17.log.success(`Switched successfully`);
|
|
3330
|
-
}
|
|
3331
|
-
const { orgId, projectId } = projectSelection;
|
|
3332
|
-
saveCredentials({
|
|
3333
|
-
org_id: orgId,
|
|
3334
|
-
project_id: projectId
|
|
3335
|
-
});
|
|
3336
|
-
}
|
|
3337
|
-
|
|
3338
|
-
// src/commands/whoami/index.ts
|
|
3339
|
-
import * as p18 from "@clack/prompts";
|
|
3340
|
-
import { UsersApi as UsersApi3 } from "@rightbrain/brain-api-client";
|
|
3341
|
-
import pc15 from "picocolors";
|
|
3342
|
-
function registerWhoamiCommand(program) {
|
|
3343
|
-
program.description("Display current user information and selected organization/project").action(runWhoami);
|
|
3344
|
-
}
|
|
3345
|
-
var userSpinner = getSpinner();
|
|
3346
|
-
async function runWhoami() {
|
|
3347
|
-
const context = await getAuthContext();
|
|
3348
|
-
if (context.filePath) {
|
|
3349
|
-
if (context.orgId && context.projectId) {
|
|
3350
|
-
userSpinner.start();
|
|
3351
|
-
const apiClient = await ApiClient.fromConfig();
|
|
3352
|
-
const [org] = await tryCatch(async () => apiClient.getOrganization(context.orgId));
|
|
3353
|
-
const [project] = await tryCatch(async () => apiClient.getProject(context.orgId, context.projectId));
|
|
3354
|
-
userSpinner.stop();
|
|
3355
|
-
p18.log.info(pc15.bold("Selected Organization & Project:"));
|
|
3356
|
-
p18.log.info(` Organization: ${pc15.cyan(org?.name || context.orgId)}`);
|
|
3357
|
-
p18.log.info(` Project: ${pc15.cyan(project?.name || context.projectId)}`);
|
|
3358
|
-
}
|
|
3359
|
-
process.exit(0);
|
|
3360
|
-
}
|
|
3361
|
-
userSpinner.start();
|
|
3362
|
-
const [user, userError] = await tryCatch(async () => {
|
|
3363
|
-
const usersApi = new UsersApi3(getConfiguration(context.accessToken));
|
|
3364
|
-
return await usersApi.getCurrentUser().then((response) => response.data);
|
|
3365
|
-
});
|
|
3366
|
-
if (userError || !user) {
|
|
3367
|
-
userSpinner.stop();
|
|
3368
|
-
p18.cancel("Failed to get user information. Please login again.");
|
|
3369
|
-
process.exit(1);
|
|
3370
|
-
}
|
|
3371
|
-
userSpinner.stop();
|
|
3372
|
-
p18.log.info(pc15.bold("User Information:"));
|
|
3373
|
-
p18.log.info(` Email: ${pc15.cyan(user.email || "N/A")}`);
|
|
3374
|
-
if (user.name) {
|
|
3375
|
-
p18.log.info(` Name: ${pc15.cyan(user.name)}`);
|
|
3376
|
-
}
|
|
3377
|
-
if (user.id) {
|
|
3378
|
-
p18.log.info(` ID: ${pc15.cyan(user.id)}`);
|
|
3379
|
-
}
|
|
3380
|
-
p18.log.info(pc15.bold("Selected Organization & Project:"));
|
|
3381
|
-
if (context.orgId && context.projectId) {
|
|
3382
|
-
userSpinner.start();
|
|
3383
|
-
const apiClient = await ApiClient.fromAuthContext();
|
|
3384
|
-
const [org] = await tryCatch(async () => apiClient.getOrganization(context.orgId));
|
|
3385
|
-
const [project] = await tryCatch(async () => apiClient.getProject(context.orgId, context.projectId));
|
|
3386
|
-
userSpinner.stop();
|
|
3387
|
-
p18.log.info(` Organization: ${pc15.cyan(org?.name || context.orgId)}`);
|
|
3388
|
-
p18.log.info(` Project: ${pc15.cyan(project?.name || context.projectId)}`);
|
|
3389
|
-
} else {
|
|
3390
|
-
p18.log.warn(pc15.yellow(" No organization and project selected"));
|
|
3391
|
-
p18.log.info(" Run `npx rightbrain switch-project` command to select an organization and project");
|
|
3392
|
-
}
|
|
3393
|
-
}
|
|
3394
|
-
|
|
3395
|
-
// src/index.ts
|
|
3396
|
-
import * as p19 from "@clack/prompts";
|
|
3397
|
-
import { Command } from "commander";
|
|
3398
|
-
import pc16 from "picocolors";
|
|
3399
|
-
async function main() {
|
|
3400
|
-
const [, error] = tryCatch(() => {
|
|
3401
|
-
const program = new Command();
|
|
3402
|
-
program.name(Object.keys(package_default.bin)[0]).description(package_default.description).version(package_default.version).option("--config <path>", "Path to configuration file").hook("preAction", (thisCommand) => {
|
|
3403
|
-
;
|
|
3404
|
-
(isPiped ? console.error : p19.intro)(pc16.dim(`RightBrain CLI ${package_default.version}`));
|
|
3405
|
-
const opts = thisCommand.opts();
|
|
3406
|
-
if (opts.config) {
|
|
3407
|
-
setConfigPath(opts.config);
|
|
3408
|
-
}
|
|
3409
|
-
if (thisCommand.args?.[0] !== "init") {
|
|
3410
|
-
const config2 = loadConfig();
|
|
3411
|
-
if (config2) {
|
|
3412
|
-
;
|
|
3413
|
-
(isPiped ? console.error : p19.log.info)(pc16.dim(`Using configuration file: ${config2.filePath}`));
|
|
3414
|
-
}
|
|
3415
|
-
}
|
|
3416
|
-
});
|
|
3417
|
-
registerLoginCommand(program.command("login"));
|
|
3418
|
-
registerLogoutCommand(program.command("logout"));
|
|
3419
|
-
registerInitCommand(program.command("init"));
|
|
3420
|
-
registerSwitchProjectCommand(program.command("switch-project"));
|
|
3421
|
-
registerWhoamiCommand(program.command("whoami"));
|
|
3422
|
-
registerCreateTaskCommand(program.command("create-task"));
|
|
3423
|
-
registerRunTaskCommand(program.command("run-task"));
|
|
3424
|
-
registerListModelsCommand(program.command("list-models"));
|
|
3425
|
-
registerGenerateCommand(program.command("generate"));
|
|
3426
|
-
program.parse(process.argv);
|
|
3427
|
-
});
|
|
3428
|
-
if (error) {
|
|
3429
|
-
if (error instanceof Error) {
|
|
3430
|
-
console.error("Error:", error.message);
|
|
3431
|
-
} else {
|
|
3432
|
-
console.error("Unknown error occurred");
|
|
3433
|
-
}
|
|
3434
|
-
process.exit(1);
|
|
3435
|
-
}
|
|
3436
|
-
}
|
|
3437
|
-
main().catch(console.error);
|
|
447
|
+
`;return te.writeFileSync(n,I,"utf-8"),m.log.success(`Added ${j.cyan(d.length.toString())} task(s) to configuration`),true}async function Vo(){Q({canRunWithOptions:false}),await Uo(process.cwd()),await Bo(process.cwd());let{selectedLanguage:e,outputDir:t}=await Oo(),{selectedProject:r,context:o}=await No(),{selectedApiKey:n,apiKeysFailed:i}=await zo({selectedProject:r});!n&&!i&&(m.cancel("Error: Failed to select or create an API key."),process.exit(1));let s=X.join(process.cwd(),st),{isEnvFileUpdated:a,configContent:c}=await qo({configFilePath:s,selectedApiKey:n,selectedProject:r,wd:process.cwd(),language:e,outputDir:t});m.log.success(`Generated ${j.cyan(st)}, and ${a?"updated ":""}${j.cyan(Ir)}`);try{await vr(e,process.cwd(),b.CLI_PUBLIC_TEMPLATES_URL);}catch(l){let d=l instanceof Error?l.message:String(l);m.log.error(j.red(`Failed to apply templates: ${d}`)),process.exit(1);}await Ko({accessToken:o.accessToken,orgId:r.orgId,projectId:r.projectId,configFilePath:s,outputDir:t,config:c,language:e})&&await ot({init:true}),m.outro(j.green("RightBrain project initialized successfully!"));}function Er(e){e.description("List available LLM models").option("-p, --provider <name>","Filter by provider (e.g., openai, anthropic)").option("--json","Output as JSON").option("-c, --compact","Compact output (ID and name only)").action(Yo);}var at=_();function Wo(e){return e>=1e6?`${(e/1e6).toFixed(1)}M`:e>=1e3?`${Math.round(e/1e3)}K`:e.toString()}async function Yo(e){at.start();let t=await T.fromAuthContext();at.stop(),at.start();let[r,o]=await p(()=>t.listModels());at.stop(),(o||!r)&&(L(o||new Error("Failed to fetch models")),process.exit(1));let n=r;n.length===0&&((f?console.error:m.outro)(j.red("No models available")),process.exit(0));let i=n.filter(c=>!c.replaced_by);if(e.provider){let c=e.provider.toLowerCase();i=i.filter(u=>u.provider.toLowerCase().includes(c)||u.vendor.toLowerCase().includes(c)),i.length===0&&((f?console.error:m.outro)(j.red(`No models found for provider: ${e.provider}`)),process.exit(0));}(e.json||f)&&(console.info(JSON.stringify(i,null,2)),process.exit(0));let s=Ue(i),a=Array.from(s.keys()).sort();if(m.log.info(j.bold(`Available Models (${i.length} total)`)),console.info(),e.compact)for(let c of a){let u=s.get(c);console.info(j.bold(j.cyan(`\u25B8 ${c}`)));for(let l of u)console.info(` ${l.alias}`),console.info(` ${j.dim(l.id)}`);console.info();}else for(let c of a){let u=s.get(c);console.info(j.bold(j.cyan(`\u25B8 ${c}`))+j.dim(` (${u.length} models)`)),console.info();for(let l of u){console.info(` ${j.bold(l.alias)}`),console.info(` ${j.dim("ID:")} ${j.green(l.id)}`);let d=[];l.vendor!==l.provider&&d.push(`via ${l.vendor}`),l.description&&d.push(l.description),l.supports_vision&&d.push(j.magenta("\u{1F441} vision")),l.max_context_window&&d.push(`${Wo(l.max_context_window)} ctx`),d.length>0&&console.info(` ${j.dim(d.join(" \xB7 "))}`),console.info();}}(!e.provider||!e.compact)&&console.info(j.dim("\u2500".repeat(40))),e.provider||console.info(j.dim("Use --provider <name> to filter by provider.")),e.compact||console.info(j.dim("Use --compact for a condensed view."));}function Tr(e){e.description("Authenticate with RightBrain").action(Go);}async function Go(){Q({canRunWithOptions:false});let e=me();if(e){let[E]=await p(async()=>await new UsersApi(O(e.access_token)).getCurrentUser().then(h=>h.data));if(E){let I=await m.confirm({message:`You are already logged in as ${j.cyan(E.email)}. Do you want to login again?`,initialValue:false});(m.isCancel(I)||!I)&&(m.outro("Login cancelled"),process.exit(0));}}let t=Yt(),r=await Zt(t),o=Ht(),n=Gt(o,r),i=Rt(),s=Dt(o,t,n),a=m.spinner();a.start("Press Enter to open browser for authentication...");let c=new Promise(E=>{let I=Ge(E);s.finally(I);});if((await Promise.race([c.then(()=>({type:"enter"})),s.then(()=>({type:"auth"}))])).type==="enter"){a.message("Opening browser for authentication...");let[,E]=await p(()=>Ye(i));E?m.log.warn(`Could not open browser automatically. Please open the URL manually.
|
|
448
|
+
${j.cyan(i)}`):a.message("Browser opened. Waiting for authentication...");}let l=await s;(!l.success||!l.token)&&(a.stop("Authentication failed"),m.cancel(`Error: Authentication failed: ${l.error||"Unknown error"}`),process.exit(1));let d=l.token.expires_in?Date.now()+l.token.expires_in*1e3:void 0;re({access_token:l.token.access_token,refresh_token:l.token.refresh_token,expires_at:d,org_id:void 0,project_id:void 0}),a.stop(j.green("You're now logged in!"));let v=await de({accessToken:l.token.access_token});re({org_id:v.orgId,project_id:v.projectId}),m.outro(j.green("Selected organization and project"));}function _r(e){e.description("Log out from RightBrain").option("-y, --yes","Skip confirmation prompt").action(Xo);}async function Xo(e){Q({canRunWithOptions:false});let t=me();if(!t){m.log.info("You are not logged in.");return}if(!e.yes){let[r]=await p(async()=>await new UsersApi(O(t.access_token)).getCurrentUser().then(n=>n.data));if(r){let o=await m.confirm({message:"Are you sure you want to log out?",initialValue:false});(m.isCancel(o)||!o)&&(m.outro("Logout cancelled"),process.exit(0));}}qt(),m.log.success(j.green("Successfully logged out!"));}var tn=Pe.url(),Ar=async(e,{providedInputNames:t,inputs:r,providedInputs:o})=>{if(t.indexOf(e.param_name)!==-1){r[e.param_name]=o[e.param_name],m.log.info(`${e.param_name}: ${j.cyan(o[e.param_name])}`);return}let i=await m.text({message:`${e.param_name} (${e.input_processor==="perplexity_search"?"Perplexity":"Text"}):`,placeholder:e.input_processor==="perplexity_search"?"e.g., Latest news about renewable energy":"e.g., Text that's contextually relevant to the task",validate:s=>{if(!s)return `${e.param_name} is required`}});m.isCancel(i)&&(m.outro("Run task cancelled"),process.exit(0)),r[e.param_name]=i;},$r={"":Ar,perplexity_search:Ar,url_fetcher:async(e,{providedInputNames:t,inputs:r,providedInputs:o})=>{if(t.indexOf(e.param_name)!==-1){r[e.param_name]=o[e.param_name],m.log.info(`${e.param_name}: ${j.cyan(o[e.param_name])}`);return}let i=await m.text({message:`${e.param_name} (Web crawler):`,placeholder:"e.g., https://docs.rightbrain.ai/llms-full.txt",validate:s=>{if(!s)return "URL is required";if(!tn.safeParse(s).success)return "Invalid URL format"}});m.isCancel(i)&&(m.outro("Run task cancelled"),process.exit(0)),r[e.param_name]=i;},document_content_extractor:async(e,{files:t,providedFileInputNames:r,providedFiles:o})=>{(!e.config||!("filename"in e.config)||typeof e.config.filename!="string")&&(m.outro(j.red(`ERROR: Unable to find input processor for ${e.param_name}`)),process.exit(1));let n=e.config.filename,i=r.indexOf(n);if(i!==-1){t.push(o[i]),m.log.info(`${e.param_name}: ${j.cyan(n)}`);return}let s=await m.text({message:`${e.param_name} (Document):`,placeholder:"path/to/document.pdf",validate:d=>{if(!d)return "File path is required";let v=Y(d);if(!te.existsSync(v))return "File not found"}});m.isCancel(s)&&(m.outro("Run task cancelled"),process.exit(0));let a=Y(s),c=te.readFileSync(a),l=(await fileTypeFromBuffer(c))?.mime||"application/octet-stream";t.push(new File([c],n,{type:l}));}};function on(e){let t=e.indexOf("=");return t===-1?null:{key:e.slice(0,t),value:e.slice(t+1)}}function Mr(e){e.description("Run a task").option("-t, --task <id>","Task ID to run").option("-r, --revision <id>","Specific revision ID").option("-i, --input <name=value>","Input parameter (repeatable)",(t,r)=>(r.push(t),r),[]).option("-f, --file <name=path>","File to upload (repeatable)",(t,r)=>(r.push(t),r),[]).option("--image <path>","Image to attach (repeatable)",(t,r)=>(r.push(t),r),[]).option("-d, --direct","Run the task directly without fetching resources first. Recommended for production environments where inputs and files are already provided.").option("--use-fallback-model","Use fallback model for the task").action(nn);}var fe=_();async function nn(e){fe.start();let t=await T.fromAuthContext();fe.stop();let r=await(e.direct||f?an(e):sn(e,t)),o=m.spinner();f?fe.start():o.start("Running task...");let[n,i]=await p(()=>t.runTask({...r,useFallbackModel:e.useFallbackModel}));fe.stop(),(f?console.error:o.stop)(i?"Failed to run task":"Task run successfully"),i&&(L(i),process.exit(1)),console.info(f?JSON.stringify(n,null,2):inspect(n,{depth:null,colors:true}));}function pt(e,t="name=value"){let r={};for(let o of e){let n=on(o);n||((f?console.error:m.outro)(j.red(`Invalid input format: ${o}. Expected ${t}`)),process.exit(1)),r[n.key]=n.value;}return r}async function Sr(e){return await Promise.all(Object.entries(e).map(async([r,o])=>{let n=Y(o);te.existsSync(n)||((f?console.error:m.outro)(j.red(`Error: File not found: ${o}`)),process.exit(1));let i=te.readFileSync(n),a=(await fileTypeFromBuffer(i))?.mime||"application/octet-stream";return new File([i],r,{type:a})}))}async function Lr(e){return await Promise.all(e.map(async r=>{let o=Y(r);te.existsSync(o)||((f?console.error:m.outro)(j.red(`Error: File not found: ${r}`)),process.exit(1));let n=te.readFileSync(o),s=(await fileTypeFromBuffer(n))?.mime||"application/octet-stream",a=X.basename(o);return new File([n],a,{type:s})}))}async function sn(e,t){if(!e.task){fe.start();let[w,C]=await p(()=>t.listTasks());fe.stop(),C&&(L(C),process.exit(1));let S=w;if((!S||S.length===0)&&((f?console.error:m.outro)(j.red("No tasks found in this project.")),(f?console.error:m.outro)("Create a task with `npx rightbrain create-task` command."),process.exit(1)),S.length===1)e.task=S[0].id,(f?console.error:m.log.info)(`Using task: ${j.cyan(S[0].name)}`);else {let P=await m.select({message:"Select a task to run",options:S.map(q=>({value:q.id,label:q.name,hint:q.id}))});m.isCancel(P)&&(m.outro("Run task cancelled"),process.exit(0)),e.task=P;}}fe.start();let[r,o]=await p(()=>t.getTask(e.task));fe.stop(),(o||!r)&&(L(o||new Error("Task not found")),process.exit(1));let n={...r,revisions:Qe(r.revisions||[])},i=n.revisions.length===1?n.revisions[0]:void 0;i||(e.revision?(i=n.revisions?.find(w=>w.id===e.revision||w.version===e.revision),i||((f?console.error:m.outro)(j.red(`Error: Revision ${e.revision} not found`)),process.exit(1))):(i=et(n),i?(f?console.error:m.log.info)("Using active revision"):((f?console.error:m.outro)(j.red("Error: No active revision found for this task. Please specify a revision ID.")),process.exit(1))));let s=i.input_params??[],a=i.input_processors??[],c=i.image_required??false,u=pt(e.input||[]),l=Object.keys(u),d=pt(e.file||[],"name=path"),v=Object.keys(d),E=await Sr(d),I={},h=[];for(let w of s){let C=a.find(P=>P.param_name===w)??{param_name:w,input_processor:""},S=$r[C.input_processor];S||((f?console.error:m.outro)(j.red(`ERROR: Unable to find input processor for ${C.param_name}`)),process.exit(1)),await S(C,{files:h,inputs:I,providedFileInputNames:v,providedFiles:E,providedInputNames:l,providedInputs:u});}let U=await Lr(e.image||[]);if(U.length>0&&!f&&m.log.info(`Image${U.length>1?"s":""}: ${j.cyan(U.map(w=>w.name).join(", "))}`),c&&U.length===0){let w=await m.text({message:"Image required - enter path:",placeholder:"path/to/image.jpg",validate:ce=>{if(!ce)return "Path is required";let De=Y(ce);if(!te.existsSync(De))return "File not found"}});m.isCancel(w)&&(m.outro("Run task cancelled"),process.exit(0));let C=Y(w),S=te.readFileSync(C),P=X.basename(C),ae=(await fileTypeFromBuffer(S))?.mime||"application/octet-stream";U.push(new File([S],P,{type:ae}));}return {taskId:n.id,revisionId:i.id,body:{task_input:I,task_files:[...U,...h]}}}async function an(e){e.task||((f?console.error:m.outro)(j.red("Error: Task ID is required when running directly")),process.exit(1)),H(e.task)||((f?console.error:m.outro)(j.red("Error: Task ID must be a valid UUID when running directly")),process.exit(1)),e.revision&&!H(e.revision)&&((f?console.error:m.outro)(j.red("Error: Revision must be a valid UUID when running directly")),process.exit(1));let t=await Lr(e.image||[]),r=await Sr(pt(e.file||[],"name=path"));return {body:{task_input:pt(e.input||[]),task_files:[...t,...r]},taskId:e.task,revisionId:e.revision}}function Fr(e){e.description("Switch to a different organization and project").option("--org-id <orgId>","Organization ID to switch to").option("--project-id <projectId>","Project ID to switch to").action(cn);}var dt=_();async function cn(e){Q();let t=await W();t.filePath&&m.log.info(j.yellow(`\u26A0\uFE0F This command is for updating the global project/organization context used by the CLI.
|
|
449
|
+
`)+" If you want to switch projects inside a specific project/config-based setup, please update the orgId and projectId values in your configuration file directly."),(e.orgId||e.projectId)&&(e.orgId||(m.cancel("Organization ID (--org-id) is required when specifying a project ID (--project-id)"),process.exit(1)),e.projectId||(m.cancel("Project ID (--project-id) is required when specifying an organization ID (--org-id)"),process.exit(1)));let r;if(e.orgId||e.projectId){dt.start();let i=e.orgId,s=e.projectId,a=new T({accessToken:t.accessToken,orgId:i,projectId:s}),[c,u]=await p(()=>a.getOrganization(i));(u||!c)&&(L(u||new Error("Organization not found")),process.exit(1)),dt.stop(),m.log.info(`\u2713 Organization access confirmed: ${j.cyan(c.name||i)}`),dt.start();let[l,d]=await p(()=>a.getProject(i,s));(d||!l)&&(L(d||new Error("Project not found")),process.exit(1)),dt.stop(),m.log.info(`\u2713 Project access confirmed: ${j.cyan(l.name||s)}`),r={orgId:i,projectId:s},m.log.success(`Switched to organization: ${j.cyan(c.name||i)}, project: ${j.cyan(l.name||s)}`);}else r=await de({...await W({loadGlobalConfig:true}),orgId:void 0,projectId:void 0}),m.log.success("Switched successfully");let{orgId:o,projectId:n}=r;re({org_id:o,project_id:n});}function Rr(e){e.description("Display current user information and selected organization/project").action(pn);}var ye=_();async function pn(){let e=await W();if(e.filePath){if(e.orgId&&e.projectId){ye.start();let o=await T.fromConfig(),[n]=await p(async()=>o.getOrganization(e.orgId)),[i]=await p(async()=>o.getProject(e.orgId,e.projectId));ye.stop(),m.log.info(j.bold("Selected Organization & Project:")),m.log.info(` Organization: ${j.cyan(n?.name||e.orgId)}`),m.log.info(` Project: ${j.cyan(i?.name||e.projectId)}`);}process.exit(0);}ye.start();let[t,r]=await p(async()=>await new UsersApi(O(e.accessToken)).getCurrentUser().then(n=>n.data));if((r||!t)&&(ye.stop(),m.cancel("Failed to get user information. Please login again."),process.exit(1)),ye.stop(),m.log.info(j.bold("User Information:")),m.log.info(` Email: ${j.cyan(t.email||"N/A")}`),t.name&&m.log.info(` Name: ${j.cyan(t.name)}`),t.id&&m.log.info(` ID: ${j.cyan(t.id)}`),m.log.info(j.bold("Selected Organization & Project:")),e.orgId&&e.projectId){ye.start();let o=await T.fromAuthContext(),[n]=await p(async()=>o.getOrganization(e.orgId)),[i]=await p(async()=>o.getProject(e.orgId,e.projectId));ye.stop(),m.log.info(` Organization: ${j.cyan(n?.name||e.orgId)}`),m.log.info(` Project: ${j.cyan(i?.name||e.projectId)}`);}else m.log.warn(j.yellow(" No organization and project selected")),m.log.info(" Run `npx rightbrain switch-project` command to select an organization and project");}async function fn(){let[,e]=p(()=>{let t=new Command;t.name(Object.keys(_e.bin)[0]).description(_e.description).version(_e.version).option("--config <path>","Path to configuration file").hook("preAction",r=>{(f?console.error:m.intro)(j.dim(`RightBrain CLI ${_e.version}`));let o=r.opts();if(o.config&&Ot(o.config),r.args?.[0]!=="init"){let n=je();n&&(f?console.error:m.log.info)(j.dim(`Using configuration file: ${n.filePath}`));}}),Tr(t.command("login")),_r(t.command("logout")),br(t.command("init")),Fr(t.command("switch-project")),Rr(t.command("whoami")),ir(t.command("create-task")),Mr(t.command("run-task")),Er(t.command("list-models")),gr(t.command("generate")),t.parse(process.argv);});e&&(e instanceof Error?console.error("Error:",e.message):console.error("Unknown error occurred"),process.exit(1));}fn().catch(console.error);
|