@qabyai/qli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +494 -0
- package/package.json +40 -0
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import yargs from "yargs";
|
|
3
|
+
import { hideBin } from "yargs/helpers";
|
|
4
|
+
import { chromium } from "playwright";
|
|
5
|
+
import { chmodSync, createWriteStream, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync } from "fs";
|
|
6
|
+
import { spawn } from "child_process";
|
|
7
|
+
import { pipeline } from "stream/promises";
|
|
8
|
+
import { basename, dirname, extname, join, resolve } from "path";
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
10
|
+
import https from "https";
|
|
11
|
+
import chalk from "chalk";
|
|
12
|
+
import { createTRPCClient, httpBatchLink } from "@trpc/client";
|
|
13
|
+
import superjson from "superjson";
|
|
14
|
+
|
|
15
|
+
//#region rolldown:runtime
|
|
16
|
+
var __defProp = Object.defineProperty;
|
|
17
|
+
var __export = (all) => {
|
|
18
|
+
let target = {};
|
|
19
|
+
for (var name in all) __defProp(target, name, {
|
|
20
|
+
get: all[name],
|
|
21
|
+
enumerable: true
|
|
22
|
+
});
|
|
23
|
+
return target;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/utils/ngrok-binary.ts
|
|
28
|
+
const NGROK_DIR = join(dirname(fileURLToPath(import.meta.url)), "..", "bin");
|
|
29
|
+
const NGROK_BINARY = join(NGROK_DIR, process.platform === "win32" ? "ngrok.exe" : "ngrok");
|
|
30
|
+
function getPlatformInfo() {
|
|
31
|
+
const platform = process.platform;
|
|
32
|
+
const arch = process.arch;
|
|
33
|
+
if (platform === "darwin") if (arch === "arm64") return {
|
|
34
|
+
downloadUrl: "https://bin.ngrok.com/c/bNyj1mQVY4c/ngrok-v3-stable-darwin-arm64.zip",
|
|
35
|
+
isArchive: true,
|
|
36
|
+
archiveType: "zip"
|
|
37
|
+
};
|
|
38
|
+
else return {
|
|
39
|
+
downloadUrl: "https://bin.ngrok.com/c/bNyj1mQVY4c/ngrok-v3-stable-darwin-amd64.zip",
|
|
40
|
+
isArchive: true,
|
|
41
|
+
archiveType: "zip"
|
|
42
|
+
};
|
|
43
|
+
else if (platform === "linux") return {
|
|
44
|
+
downloadUrl: "https://bin.ngrok.com/c/bNyj1mQVY4c/ngrok-v3-stable-linux-amd64.tgz",
|
|
45
|
+
isArchive: true,
|
|
46
|
+
archiveType: "tgz"
|
|
47
|
+
};
|
|
48
|
+
else if (platform === "win32") return {
|
|
49
|
+
downloadUrl: "https://bin.ngrok.com/c/bNyj1mQVY4c/ngrok-v3-stable-windows-amd64.zip",
|
|
50
|
+
isArchive: true,
|
|
51
|
+
archiveType: "zip"
|
|
52
|
+
};
|
|
53
|
+
else throw new Error(`Unsupported platform: ${platform}`);
|
|
54
|
+
}
|
|
55
|
+
async function downloadFile(url, filePath) {
|
|
56
|
+
return new Promise((resolve$1, reject) => {
|
|
57
|
+
https.get(url, (response) => {
|
|
58
|
+
if (response.statusCode !== 200) {
|
|
59
|
+
reject(/* @__PURE__ */ new Error(`Failed to download: ${response.statusCode}`));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
pipeline(response, createWriteStream(filePath)).then(() => resolve$1()).catch(reject);
|
|
63
|
+
}).on("error", reject);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
async function extractArchive(archivePath, extractDir, type) {
|
|
67
|
+
return new Promise((resolve$1, reject) => {
|
|
68
|
+
let command$2;
|
|
69
|
+
let args;
|
|
70
|
+
if (type === "zip") {
|
|
71
|
+
command$2 = "unzip";
|
|
72
|
+
args = [
|
|
73
|
+
"-o",
|
|
74
|
+
archivePath,
|
|
75
|
+
"-d",
|
|
76
|
+
extractDir
|
|
77
|
+
];
|
|
78
|
+
} else {
|
|
79
|
+
command$2 = "tar";
|
|
80
|
+
args = [
|
|
81
|
+
"-xzf",
|
|
82
|
+
archivePath,
|
|
83
|
+
"-C",
|
|
84
|
+
extractDir
|
|
85
|
+
];
|
|
86
|
+
}
|
|
87
|
+
const process$1 = spawn(command$2, args);
|
|
88
|
+
process$1.on("close", (code) => {
|
|
89
|
+
if (code === 0) resolve$1();
|
|
90
|
+
else reject(/* @__PURE__ */ new Error(`Extraction failed with code ${code}`));
|
|
91
|
+
});
|
|
92
|
+
process$1.on("error", reject);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
async function ensureNgrokBinary() {
|
|
96
|
+
if (existsSync(NGROK_BINARY)) return NGROK_BINARY;
|
|
97
|
+
if (!existsSync(NGROK_DIR)) mkdirSync(NGROK_DIR, { recursive: true });
|
|
98
|
+
const platformInfo = getPlatformInfo();
|
|
99
|
+
const tempArchivePath = join(NGROK_DIR, `ngrok.${platformInfo.archiveType}`);
|
|
100
|
+
try {
|
|
101
|
+
console.log("📦 Downloading ngrok binary...");
|
|
102
|
+
await downloadFile(platformInfo.downloadUrl, tempArchivePath);
|
|
103
|
+
console.log("📂 Extracting ngrok binary...");
|
|
104
|
+
await extractArchive(tempArchivePath, NGROK_DIR, platformInfo.archiveType);
|
|
105
|
+
if (existsSync(NGROK_BINARY)) {
|
|
106
|
+
chmodSync(NGROK_BINARY, 493);
|
|
107
|
+
rmSync(tempArchivePath);
|
|
108
|
+
return NGROK_BINARY;
|
|
109
|
+
} else throw new Error(`Binary not found after extraction. Expected: ${NGROK_BINARY}`);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
if (existsSync(tempArchivePath)) rmSync(tempArchivePath);
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async function startNgrokTunnel(port, authtoken) {
|
|
116
|
+
return new Promise((resolve$1, reject) => {
|
|
117
|
+
const initTunnel = async () => {
|
|
118
|
+
try {
|
|
119
|
+
const ngrokProcess = spawn(await ensureNgrokBinary(), [
|
|
120
|
+
"http",
|
|
121
|
+
port.toString(),
|
|
122
|
+
"--authtoken",
|
|
123
|
+
authtoken,
|
|
124
|
+
"--log=stdout",
|
|
125
|
+
"--log-format=json"
|
|
126
|
+
], { stdio: [
|
|
127
|
+
"pipe",
|
|
128
|
+
"pipe",
|
|
129
|
+
"pipe"
|
|
130
|
+
] });
|
|
131
|
+
let output = "";
|
|
132
|
+
let errorOutput = "";
|
|
133
|
+
let tunnelUrl = "";
|
|
134
|
+
let resolved = false;
|
|
135
|
+
ngrokProcess.stdout?.on("data", (data) => {
|
|
136
|
+
const chunk = data.toString();
|
|
137
|
+
output += chunk;
|
|
138
|
+
const urlMatch = chunk.match(/"url":"(https:\/\/[^"]+)"/);
|
|
139
|
+
if (urlMatch && !tunnelUrl && !resolved) {
|
|
140
|
+
tunnelUrl = urlMatch[1];
|
|
141
|
+
resolved = true;
|
|
142
|
+
resolve$1({
|
|
143
|
+
url: tunnelUrl,
|
|
144
|
+
process: ngrokProcess
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
ngrokProcess.stderr?.on("data", (data) => {
|
|
149
|
+
const chunk = data.toString();
|
|
150
|
+
errorOutput += chunk;
|
|
151
|
+
if (chunk.includes("ERRO") || chunk.includes("failed to")) {
|
|
152
|
+
if (!resolved) {
|
|
153
|
+
resolved = true;
|
|
154
|
+
reject(/* @__PURE__ */ new Error(`Ngrok error: ${chunk}`));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
ngrokProcess.on("error", (error) => {
|
|
159
|
+
if (!resolved) {
|
|
160
|
+
resolved = true;
|
|
161
|
+
reject(/* @__PURE__ */ new Error(`Failed to start ngrok: ${error.message}`));
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
ngrokProcess.on("exit", (code) => {
|
|
165
|
+
if (code !== 0 && !resolved) {
|
|
166
|
+
resolved = true;
|
|
167
|
+
reject(/* @__PURE__ */ new Error(`Ngrok exited with code ${code}`));
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
setTimeout(() => {
|
|
171
|
+
if (!resolved) {
|
|
172
|
+
ngrokProcess.kill();
|
|
173
|
+
resolved = true;
|
|
174
|
+
reject(/* @__PURE__ */ new Error("Timeout waiting for ngrok tunnel URL"));
|
|
175
|
+
}
|
|
176
|
+
}, 2e4);
|
|
177
|
+
} catch (error) {
|
|
178
|
+
reject(error);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
initTunnel();
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
//#endregion
|
|
186
|
+
//#region src/commands/browser.ts
|
|
187
|
+
var browser_exports = /* @__PURE__ */ __export({
|
|
188
|
+
builder: () => builder$1,
|
|
189
|
+
command: () => command$1,
|
|
190
|
+
desc: () => desc$1,
|
|
191
|
+
handler: () => handler$1
|
|
192
|
+
});
|
|
193
|
+
const command$1 = "browser";
|
|
194
|
+
const desc$1 = "Start Playwright browser server with ngrok tunnel";
|
|
195
|
+
const builder$1 = (yargs$1) => yargs$1.option("port", {
|
|
196
|
+
type: "number",
|
|
197
|
+
default: 9222,
|
|
198
|
+
describe: "Port to run the browser server on"
|
|
199
|
+
}).option("headless", {
|
|
200
|
+
type: "boolean",
|
|
201
|
+
default: false,
|
|
202
|
+
describe: "Run browser in headless mode"
|
|
203
|
+
});
|
|
204
|
+
const handler$1 = async (argv) => {
|
|
205
|
+
const { port = 9222, headless = false } = argv;
|
|
206
|
+
try {
|
|
207
|
+
console.log(chalk.gray("[debug] Starting Playwright browser server..."));
|
|
208
|
+
const browserServer = await chromium.launchServer({
|
|
209
|
+
headless,
|
|
210
|
+
port,
|
|
211
|
+
args: [
|
|
212
|
+
"--no-sandbox",
|
|
213
|
+
"--disable-setuid-sandbox",
|
|
214
|
+
"--disable-web-security",
|
|
215
|
+
"--disable-features=VizDisplayCompositor"
|
|
216
|
+
]
|
|
217
|
+
});
|
|
218
|
+
const wsEndpoint = browserServer.wsEndpoint();
|
|
219
|
+
console.log(chalk.gray("[debug] Browser server started successfully!"));
|
|
220
|
+
console.log(chalk.gray(`[debug] WebSocket endpoint: ${wsEndpoint}`));
|
|
221
|
+
const sessionId = wsEndpoint.split("/").pop() || "";
|
|
222
|
+
let tunnelUrl;
|
|
223
|
+
let ngrokProcess;
|
|
224
|
+
try {
|
|
225
|
+
console.log(chalk.gray("[debug] Creating secure tunnel..."));
|
|
226
|
+
const result = await startNgrokTunnel(port, "38bZ22nMOQFcUhqpTi3SFIphrjX_68vsN6T945JmNVaNGYjHs");
|
|
227
|
+
tunnelUrl = result.url;
|
|
228
|
+
ngrokProcess = result.process;
|
|
229
|
+
console.log(chalk.gray("[debug] Tunnel established!"));
|
|
230
|
+
const completeWsUrl = `${tunnelUrl.replace("https://", "wss://")}/${sessionId}`;
|
|
231
|
+
console.log(chalk.green.bold(`\n${completeWsUrl}\n`));
|
|
232
|
+
console.log(chalk.cyan("💡 Copy the URL above and paste it in your browser"));
|
|
233
|
+
} catch (error) {
|
|
234
|
+
console.error(chalk.red("[debug] Failed to create tunnel:"));
|
|
235
|
+
console.error(chalk.red(`[debug] ${error.message || error}`));
|
|
236
|
+
console.log(chalk.gray("[debug] → Browser server will continue running locally only"));
|
|
237
|
+
console.log(chalk.gray("[debug] → Check your internet connection or try again later"));
|
|
238
|
+
console.log(chalk.green.bold(`\nws://localhost:${port}/${sessionId}\n`));
|
|
239
|
+
console.log(chalk.cyan("💡 Copy the URL above and paste it in your browser (local only)"));
|
|
240
|
+
}
|
|
241
|
+
console.log(chalk.dim("\nPress Ctrl+C to stop"));
|
|
242
|
+
const shutdown = async () => {
|
|
243
|
+
console.log(chalk.gray("\n[debug] Shutting down..."));
|
|
244
|
+
if (ngrokProcess) try {
|
|
245
|
+
ngrokProcess.kill("SIGTERM");
|
|
246
|
+
console.log(chalk.gray("[debug] ✅ Tunnel closed"));
|
|
247
|
+
} catch (error) {
|
|
248
|
+
console.log(chalk.gray(`[debug] ⚠️ Error closing tunnel: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
249
|
+
}
|
|
250
|
+
try {
|
|
251
|
+
await browserServer.close();
|
|
252
|
+
console.log(chalk.gray("[debug] ✅ Browser server closed"));
|
|
253
|
+
} catch (error) {
|
|
254
|
+
console.log(chalk.gray(`[debug] ❌ Error closing browser server: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
255
|
+
}
|
|
256
|
+
console.log(chalk.gray("\nGoodbye! 👋"));
|
|
257
|
+
process.exit(0);
|
|
258
|
+
};
|
|
259
|
+
process.on("SIGINT", shutdown);
|
|
260
|
+
process.on("SIGTERM", shutdown);
|
|
261
|
+
} catch (error) {
|
|
262
|
+
console.error(chalk.red("❌ Failed to start browser server:"), error);
|
|
263
|
+
process.exit(1);
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
//#endregion
|
|
268
|
+
//#region src/api/client.ts
|
|
269
|
+
function createClient(apiKey, apiUrl) {
|
|
270
|
+
return createTRPCClient({ links: [httpBatchLink({
|
|
271
|
+
url: `${apiUrl}/trpc`,
|
|
272
|
+
headers() {
|
|
273
|
+
return { "x-api-key": apiKey };
|
|
274
|
+
},
|
|
275
|
+
transformer: superjson
|
|
276
|
+
})] });
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
//#endregion
|
|
280
|
+
//#region src/commands/context.ts
|
|
281
|
+
var context_exports = /* @__PURE__ */ __export({
|
|
282
|
+
builder: () => builder,
|
|
283
|
+
command: () => command,
|
|
284
|
+
desc: () => desc,
|
|
285
|
+
handler: () => handler
|
|
286
|
+
});
|
|
287
|
+
const command = "context";
|
|
288
|
+
const desc = "Scan and upload codebase to generate context documents";
|
|
289
|
+
const builder = (yargs$1) => yargs$1.option("api-key", {
|
|
290
|
+
type: "string",
|
|
291
|
+
describe: "API key for authentication (or set QABYAI_API_KEY env var)"
|
|
292
|
+
}).option("dir", {
|
|
293
|
+
type: "string",
|
|
294
|
+
describe: "Directory to scan (defaults to current directory)"
|
|
295
|
+
});
|
|
296
|
+
const INCLUDE_DIR_PATTERNS = [
|
|
297
|
+
"src/",
|
|
298
|
+
"app/",
|
|
299
|
+
"pages/",
|
|
300
|
+
"lib/",
|
|
301
|
+
"routes/",
|
|
302
|
+
"components/",
|
|
303
|
+
"models/",
|
|
304
|
+
"api/"
|
|
305
|
+
];
|
|
306
|
+
const INCLUDE_FILE_EXACT = [
|
|
307
|
+
"package.json",
|
|
308
|
+
"tsconfig.json",
|
|
309
|
+
"README.md",
|
|
310
|
+
".env.example"
|
|
311
|
+
];
|
|
312
|
+
const INCLUDE_FILE_SUFFIXES = [
|
|
313
|
+
".config.ts",
|
|
314
|
+
".config.js",
|
|
315
|
+
".config.mjs"
|
|
316
|
+
];
|
|
317
|
+
const EXCLUDE_DIRS = new Set([
|
|
318
|
+
"node_modules",
|
|
319
|
+
"dist",
|
|
320
|
+
"build",
|
|
321
|
+
".next",
|
|
322
|
+
".git",
|
|
323
|
+
"__snapshots__",
|
|
324
|
+
".turbo",
|
|
325
|
+
".cache",
|
|
326
|
+
"coverage"
|
|
327
|
+
]);
|
|
328
|
+
const EXCLUDE_EXTENSIONS = new Set([
|
|
329
|
+
".lock",
|
|
330
|
+
".png",
|
|
331
|
+
".jpg",
|
|
332
|
+
".jpeg",
|
|
333
|
+
".gif",
|
|
334
|
+
".ico",
|
|
335
|
+
".svg",
|
|
336
|
+
".woff",
|
|
337
|
+
".woff2",
|
|
338
|
+
".ttf",
|
|
339
|
+
".eot",
|
|
340
|
+
".mp4",
|
|
341
|
+
".mp3",
|
|
342
|
+
".webm",
|
|
343
|
+
".webp",
|
|
344
|
+
".avif",
|
|
345
|
+
".snap",
|
|
346
|
+
".map"
|
|
347
|
+
]);
|
|
348
|
+
const EXCLUDE_FILES = new Set([
|
|
349
|
+
"pnpm-lock.yaml",
|
|
350
|
+
"package-lock.json",
|
|
351
|
+
"yarn.lock"
|
|
352
|
+
]);
|
|
353
|
+
const MAX_FILE_SIZE = 50 * 1024;
|
|
354
|
+
const MAX_TOTAL_SIZE = 5 * 1024 * 1024;
|
|
355
|
+
function shouldIncludeFile(relativePath, fileSize) {
|
|
356
|
+
const name = basename(relativePath);
|
|
357
|
+
const ext = extname(relativePath).toLowerCase();
|
|
358
|
+
if (EXCLUDE_FILES.has(name)) return false;
|
|
359
|
+
if (EXCLUDE_EXTENSIONS.has(ext)) return false;
|
|
360
|
+
for (const dir of EXCLUDE_DIRS) if (relativePath.includes(dir + "/")) return false;
|
|
361
|
+
if (fileSize > MAX_FILE_SIZE) return false;
|
|
362
|
+
for (const dir of INCLUDE_DIR_PATTERNS) if (relativePath.startsWith(dir) || relativePath.includes(`/${dir}`)) return true;
|
|
363
|
+
if (INCLUDE_FILE_EXACT.includes(name)) return true;
|
|
364
|
+
for (const suffix of INCLUDE_FILE_SUFFIXES) if (name.endsWith(suffix)) return true;
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
function scanDirectory(rootDir) {
|
|
368
|
+
const files = [];
|
|
369
|
+
let totalSize = 0;
|
|
370
|
+
function walk(dir, relativeBase) {
|
|
371
|
+
let entries;
|
|
372
|
+
try {
|
|
373
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
374
|
+
} catch {
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
for (const entry of entries) if (entry.isDirectory()) {
|
|
378
|
+
if (EXCLUDE_DIRS.has(entry.name)) continue;
|
|
379
|
+
walk(join(dir, entry.name), relativeBase ? `${relativeBase}/${entry.name}` : entry.name);
|
|
380
|
+
} else if (entry.isFile()) {
|
|
381
|
+
const fullPath = join(dir, entry.name);
|
|
382
|
+
const relativePath = relativeBase ? `${relativeBase}/${entry.name}` : entry.name;
|
|
383
|
+
let stat;
|
|
384
|
+
try {
|
|
385
|
+
stat = statSync(fullPath);
|
|
386
|
+
} catch {
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
if (totalSize + stat.size > MAX_TOTAL_SIZE) continue;
|
|
390
|
+
if (shouldIncludeFile(relativePath, stat.size)) try {
|
|
391
|
+
const content = readFileSync(fullPath, "utf-8");
|
|
392
|
+
files.push({
|
|
393
|
+
path: relativePath,
|
|
394
|
+
content
|
|
395
|
+
});
|
|
396
|
+
totalSize += stat.size;
|
|
397
|
+
} catch {}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
walk(rootDir, "");
|
|
401
|
+
return files;
|
|
402
|
+
}
|
|
403
|
+
function sleep(ms) {
|
|
404
|
+
return new Promise((resolve$1) => setTimeout(resolve$1, ms));
|
|
405
|
+
}
|
|
406
|
+
const handler = async (argv) => {
|
|
407
|
+
const apiKey = argv["api-key"] || process.env.QABYAI_API_KEY;
|
|
408
|
+
if (!apiKey) {
|
|
409
|
+
console.error(chalk.red("API key required. Use --api-key flag or set QABYAI_API_KEY env var."));
|
|
410
|
+
console.error(chalk.gray("Get your API key at https://qaby.ai/settings/api-keys"));
|
|
411
|
+
process.exit(1);
|
|
412
|
+
}
|
|
413
|
+
const dir = resolve(argv.dir || process.cwd());
|
|
414
|
+
const apiUrl = process.env.QABYAI_API_URL || "https://qaby.ai";
|
|
415
|
+
const dirName = basename(dir);
|
|
416
|
+
const client = createClient(apiKey, apiUrl);
|
|
417
|
+
console.log(chalk.gray(`Scanning ${dir}...\n`));
|
|
418
|
+
const files = scanDirectory(dir);
|
|
419
|
+
if (files.length === 0) {
|
|
420
|
+
console.error(chalk.red("No matching files found. Make sure you are in a project directory."));
|
|
421
|
+
process.exit(1);
|
|
422
|
+
}
|
|
423
|
+
let jobId;
|
|
424
|
+
try {
|
|
425
|
+
jobId = (await client.context.cliScan.mutate({
|
|
426
|
+
dirName,
|
|
427
|
+
files
|
|
428
|
+
})).jobId;
|
|
429
|
+
} catch (err) {
|
|
430
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
431
|
+
console.error(chalk.red(`Failed: ${message}`));
|
|
432
|
+
process.exit(1);
|
|
433
|
+
}
|
|
434
|
+
console.log(chalk.white.bold("Generating knowledge base...\n"));
|
|
435
|
+
const spinnerFrames = [
|
|
436
|
+
"⠋",
|
|
437
|
+
"⠙",
|
|
438
|
+
"⠹",
|
|
439
|
+
"⠸",
|
|
440
|
+
"⠼",
|
|
441
|
+
"⠴",
|
|
442
|
+
"⠦",
|
|
443
|
+
"⠧",
|
|
444
|
+
"⠇",
|
|
445
|
+
"⠏"
|
|
446
|
+
];
|
|
447
|
+
let frame = 0;
|
|
448
|
+
let lastStatus = "";
|
|
449
|
+
while (true) {
|
|
450
|
+
await sleep(3e3);
|
|
451
|
+
try {
|
|
452
|
+
const status = await client.context.cliScanStatus.query({ jobId });
|
|
453
|
+
if (status.status !== lastStatus) {
|
|
454
|
+
if (lastStatus) process.stdout.write("\r\x1B[K");
|
|
455
|
+
if (status.status === "pending") console.log(chalk.gray(` Queued for processing`));
|
|
456
|
+
else if (status.status === "running") console.log(chalk.gray(` Analyzing codebase and generating docs`));
|
|
457
|
+
lastStatus = status.status;
|
|
458
|
+
}
|
|
459
|
+
if (status.status === "completed") {
|
|
460
|
+
process.stdout.write("\r\x1B[K");
|
|
461
|
+
const docCount = status.generatedDocuments ?? 0;
|
|
462
|
+
console.log(chalk.green(` Created ${docCount} document${docCount !== 1 ? "s" : ""}`));
|
|
463
|
+
console.log(chalk.green("\nDone!"));
|
|
464
|
+
console.log(chalk.cyan("View documents at: https://qaby.ai/context/documents"));
|
|
465
|
+
break;
|
|
466
|
+
}
|
|
467
|
+
if (status.status === "failed") {
|
|
468
|
+
process.stdout.write("\r\x1B[K");
|
|
469
|
+
console.error(chalk.red("\nKnowledge generation failed."));
|
|
470
|
+
if (status.error) console.error(chalk.red(status.error));
|
|
471
|
+
process.exit(1);
|
|
472
|
+
}
|
|
473
|
+
process.stdout.write(`\r ${chalk.blue(spinnerFrames[frame++ % spinnerFrames.length])} ${chalk.gray("Processing...")}`);
|
|
474
|
+
} catch {
|
|
475
|
+
process.stdout.write(`\r ${chalk.blue(spinnerFrames[frame++ % spinnerFrames.length])} ${chalk.gray("Processing...")}`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
//#endregion
|
|
481
|
+
//#region src/index.ts
|
|
482
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
483
|
+
const packageJson = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8"));
|
|
484
|
+
async function main() {
|
|
485
|
+
console.log(chalk.blue.bold(`🤖 QAbyAI CLI v${packageJson.version}\n`));
|
|
486
|
+
yargs(hideBin(process.argv)).scriptName("qli").usage("$0 <command> [options]").command(browser_exports).command(context_exports).demandCommand(1, "You need to specify a command").strict().help().alias("help", "h").version(packageJson.version).alias("version", "v").completion().parse();
|
|
487
|
+
}
|
|
488
|
+
main().catch((error) => {
|
|
489
|
+
console.error(chalk.red("❌ Fatal error:"), error);
|
|
490
|
+
process.exit(1);
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
//#endregion
|
|
494
|
+
export { };
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@qabyai/qli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "QAbyAI CLI tool for browser automation",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=14.0.0"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"qli": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"type": "module",
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@trpc/client": "^11.6.0",
|
|
20
|
+
"chalk": "^5.3.0",
|
|
21
|
+
"playwright": "^1.58.0",
|
|
22
|
+
"superjson": "^2.2.6",
|
|
23
|
+
"yargs": "^17.7.2"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@miloas/tsdown": "^0.13.0",
|
|
27
|
+
"@types/node": "^20.11.20",
|
|
28
|
+
"@types/yargs": "^17.0.32",
|
|
29
|
+
"typescript": "^5.9.3",
|
|
30
|
+
"backend": "1.0.0"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"check": "tsgo --noEmit",
|
|
34
|
+
"build": "tsdown",
|
|
35
|
+
"serve:build": "tsdown",
|
|
36
|
+
"serve:watch": "tsdown --watch",
|
|
37
|
+
"dev": "node dist/index.js",
|
|
38
|
+
"cli": "node dist/index.js"
|
|
39
|
+
}
|
|
40
|
+
}
|