@qverisai/cli 0.2.0 → 0.3.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/README.md +1 -1
- package/package.json +1 -1
- package/scripts/install.sh +1 -1
- package/src/commands/interactive.mjs +7 -1
- package/src/commands/login.mjs +7 -1
- package/src/main.mjs +12 -3
- package/src/output/banner.mjs +241 -0
- package/src/output/colors.mjs +9 -7
package/README.md
CHANGED
package/package.json
CHANGED
package/scripts/install.sh
CHANGED
|
@@ -5,7 +5,9 @@ import { resolveParams } from "../utils/params.mjs";
|
|
|
5
5
|
import { formatDiscoverResult, formatInspectResult, formatCallResult } from "../output/formatter.mjs";
|
|
6
6
|
import { generateSnippet } from "../output/codegen.mjs";
|
|
7
7
|
import { writeSession } from "../session/session.mjs";
|
|
8
|
+
import { VERSION } from "../config/defaults.mjs";
|
|
8
9
|
import { bold, dim, cyan } from "../output/colors.mjs";
|
|
10
|
+
import { printWelcomeBanner } from "../output/banner.mjs";
|
|
9
11
|
import { handleError } from "../errors/handler.mjs";
|
|
10
12
|
import { createSpinner } from "../output/spinner.mjs";
|
|
11
13
|
|
|
@@ -22,7 +24,11 @@ export async function runInteractive(flags) {
|
|
|
22
24
|
lastCallContext: null,
|
|
23
25
|
};
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
if (!flags.json) {
|
|
28
|
+
printWelcomeBanner({ version: VERSION, noColor: flags.noColor, compact: true });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
console.log(` ${bold("QVeris Interactive Mode")}`);
|
|
26
32
|
console.log(` ${dim("Type 'help' for commands, 'exit' to quit.")}\n`);
|
|
27
33
|
|
|
28
34
|
const rl = createInterface({
|
package/src/commands/login.mjs
CHANGED
|
@@ -4,7 +4,9 @@ import { platform } from "node:os";
|
|
|
4
4
|
import { resolve } from "../config/resolve.mjs";
|
|
5
5
|
import { setConfigValue, deleteConfigValue } from "../config/store.mjs";
|
|
6
6
|
import { discoverTools } from "../client/api.mjs";
|
|
7
|
+
import { VERSION } from "../config/defaults.mjs";
|
|
7
8
|
import { bold, green, red, dim, cyan } from "../output/colors.mjs";
|
|
9
|
+
import { printLoginBanner } from "../output/banner.mjs";
|
|
8
10
|
|
|
9
11
|
const ACCOUNT_URL = "https://qveris.ai/account?page=api-keys";
|
|
10
12
|
|
|
@@ -91,7 +93,11 @@ export async function runLogin(flags) {
|
|
|
91
93
|
return;
|
|
92
94
|
}
|
|
93
95
|
|
|
94
|
-
|
|
96
|
+
if (!flags.json) {
|
|
97
|
+
printLoginBanner({ version: VERSION, noColor: flags.noColor });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
console.log(` Get your API key at: ${cyan(ACCOUNT_URL)}\n`);
|
|
95
101
|
|
|
96
102
|
if (!flags.noBrowser) {
|
|
97
103
|
openBrowser(ACCOUNT_URL);
|
package/src/main.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { normalizeLegacyArgs } from "./compat/aliases.mjs";
|
|
|
2
2
|
import { handleError } from "./errors/handler.mjs";
|
|
3
3
|
import { VERSION } from "./config/defaults.mjs";
|
|
4
4
|
import { bold, dim, cyan } from "./output/colors.mjs";
|
|
5
|
+
import { printWelcomeBanner } from "./output/banner.mjs";
|
|
5
6
|
|
|
6
7
|
export async function main(argv) {
|
|
7
8
|
const rawArgs = argv.slice(2);
|
|
@@ -12,6 +13,10 @@ export async function main(argv) {
|
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
const flags = extractGlobalFlags(args);
|
|
16
|
+
if (flags.noColor) {
|
|
17
|
+
process.env.NO_COLOR = "1";
|
|
18
|
+
}
|
|
19
|
+
|
|
15
20
|
const positional = flags._positional;
|
|
16
21
|
const command = positional[0];
|
|
17
22
|
const rest = positional.slice(1);
|
|
@@ -22,7 +27,7 @@ export async function main(argv) {
|
|
|
22
27
|
}
|
|
23
28
|
|
|
24
29
|
if (!command || flags.help) {
|
|
25
|
-
printUsage();
|
|
30
|
+
printUsage(flags);
|
|
26
31
|
return;
|
|
27
32
|
}
|
|
28
33
|
|
|
@@ -202,9 +207,13 @@ function extractGlobalFlags(args) {
|
|
|
202
207
|
return flags;
|
|
203
208
|
}
|
|
204
209
|
|
|
205
|
-
function printUsage() {
|
|
210
|
+
function printUsage(flags = {}) {
|
|
211
|
+
if (!flags.json) {
|
|
212
|
+
printWelcomeBanner({ version: VERSION, noColor: flags.noColor, compact: false });
|
|
213
|
+
}
|
|
214
|
+
|
|
206
215
|
console.log(`
|
|
207
|
-
${bold("QVeris CLI")} ${dim(
|
|
216
|
+
${bold("QVeris CLI")} — ${dim("discover, inspect, and call 10,000+ capabilities")}
|
|
208
217
|
|
|
209
218
|
${bold("Usage:")}
|
|
210
219
|
qveris <command> [args] [flags]
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Welcome / login banners: gradient on solid block letters (brand cyan → indigo).
|
|
3
|
+
* Figlet font "Banner3" (filled #), mapped to █ for stronger contrast.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
import { cwd as processCwd } from "node:process";
|
|
8
|
+
import { dim, cyan, isColorEnabled } from "./colors.mjs";
|
|
9
|
+
|
|
10
|
+
/** @type {readonly [number, number, number][]} brand stops: highlight → mid → shadow */
|
|
11
|
+
const BRAND_STOPS = [
|
|
12
|
+
[0, 242, 255], // #00F2FF
|
|
13
|
+
[0, 153, 255], // #0099FF
|
|
14
|
+
[26, 0, 255], // #1A00FF
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const RESET = "\x1b[0m";
|
|
18
|
+
|
|
19
|
+
/** Figlet -f Banner3 QVERIS (53 cols × 7 rows). # → █ at render. */
|
|
20
|
+
const ASCII_QVERIS_HASH = [
|
|
21
|
+
" ####### ## ## ######## ######## #### ###### ",
|
|
22
|
+
"## ## ## ## ## ## ## ## ## ## ",
|
|
23
|
+
"## ## ## ## ## ## ## ## ## ",
|
|
24
|
+
"## ## ## ## ###### ######## ## ###### ",
|
|
25
|
+
"## ## ## ## ## ## ## ## ## ## ",
|
|
26
|
+
"## ## ## ## ## ## ## ## ## ## ",
|
|
27
|
+
" ##### ## ### ######## ## ## #### ###### ",
|
|
28
|
+
].join("\n");
|
|
29
|
+
|
|
30
|
+
function solidBlockLines() {
|
|
31
|
+
return ASCII_QVERIS_HASH.split("\n").map((line) => line.replace(/#/g, "█"));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function colorDepth() {
|
|
35
|
+
try {
|
|
36
|
+
return typeof process.stdout.getColorDepth === "function"
|
|
37
|
+
? process.stdout.getColorDepth()
|
|
38
|
+
: 8;
|
|
39
|
+
} catch {
|
|
40
|
+
return 8;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function lerp(a, b, t) {
|
|
45
|
+
return Math.round(a + (b - a) * t);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** RGB along multi-stop gradient, t in [0,1]. */
|
|
49
|
+
function rgbAt(t) {
|
|
50
|
+
const n = BRAND_STOPS.length - 1;
|
|
51
|
+
const x = Math.min(1, Math.max(0, t)) * n;
|
|
52
|
+
const i = Math.min(Math.floor(x), n - 1);
|
|
53
|
+
const f = x - i;
|
|
54
|
+
const [r1, g1, b1] = BRAND_STOPS[i];
|
|
55
|
+
const [r2, g2, b2] = BRAND_STOPS[i + 1];
|
|
56
|
+
return [lerp(r1, r2, f), lerp(g1, g2, f), lerp(b1, b2, f)];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function truecolorFg(r, g, b) {
|
|
60
|
+
return `\x1b[38;2;${r};${g};${b}m`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Map column index to gradient color (horizontal sweep across the banner).
|
|
65
|
+
* Spaces stay uncolored. Consecutive non-space chars with identical RGB share one SGR span.
|
|
66
|
+
*/
|
|
67
|
+
function paintLineTruecolor(line, maxCols) {
|
|
68
|
+
if (!line.length) return "";
|
|
69
|
+
const denom = Math.max(1, maxCols - 1);
|
|
70
|
+
let out = "";
|
|
71
|
+
let i = 0;
|
|
72
|
+
while (i < line.length) {
|
|
73
|
+
const ch = line[i];
|
|
74
|
+
if (ch === " ") {
|
|
75
|
+
out += ch;
|
|
76
|
+
i++;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const t0 = i / denom;
|
|
80
|
+
const [r0, g0, b0] = rgbAt(t0);
|
|
81
|
+
let j = i + 1;
|
|
82
|
+
while (j < line.length) {
|
|
83
|
+
const chj = line[j];
|
|
84
|
+
if (chj === " ") break;
|
|
85
|
+
const t = j / denom;
|
|
86
|
+
const [r, g, b] = rgbAt(t);
|
|
87
|
+
if (r !== r0 || g !== g0 || b !== b0) break;
|
|
88
|
+
j++;
|
|
89
|
+
}
|
|
90
|
+
out += truecolorFg(r0, g0, b0) + line.slice(i, j) + RESET;
|
|
91
|
+
i = j;
|
|
92
|
+
}
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** 16-color fallback: batched SGR per band (cyan / bold cyan / dim cyan). */
|
|
97
|
+
function paintLineFallback(line, maxCols) {
|
|
98
|
+
if (!isColorEnabled()) return line;
|
|
99
|
+
const denom = Math.max(1, maxCols - 1);
|
|
100
|
+
let out = "";
|
|
101
|
+
let i = 0;
|
|
102
|
+
while (i < line.length) {
|
|
103
|
+
const ch = line[i];
|
|
104
|
+
if (ch === " ") {
|
|
105
|
+
out += ch;
|
|
106
|
+
i++;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const band0 = Math.min(2, Math.floor((i / denom) * 3));
|
|
110
|
+
let j = i + 1;
|
|
111
|
+
while (j < line.length) {
|
|
112
|
+
const chj = line[j];
|
|
113
|
+
if (chj === " ") break;
|
|
114
|
+
const band = Math.min(2, Math.floor((j / denom) * 3));
|
|
115
|
+
if (band !== band0) break;
|
|
116
|
+
j++;
|
|
117
|
+
}
|
|
118
|
+
const chunk = line.slice(i, j);
|
|
119
|
+
if (band0 === 0) out += `\x1b[36m${chunk}\x1b[0m`;
|
|
120
|
+
else if (band0 === 1) out += `\x1b[1;36m${chunk}\x1b[0m`;
|
|
121
|
+
else out += `\x1b[2;36m${chunk}\x1b[0m`;
|
|
122
|
+
i = j;
|
|
123
|
+
}
|
|
124
|
+
return out;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function bannerColorAllowed(noColorFlag) {
|
|
128
|
+
if (noColorFlag) return false;
|
|
129
|
+
return isColorEnabled();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function shortenPath(p) {
|
|
133
|
+
const home = homedir();
|
|
134
|
+
if (p === home) return "~";
|
|
135
|
+
if (p.startsWith(home + "/")) return "~" + p.slice(home.length);
|
|
136
|
+
return p;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Strip SGR ANSI sequences (truecolor / 16-color). */
|
|
140
|
+
function stripAnsi(s) {
|
|
141
|
+
return s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const RE_HAN = /\p{Script=Han}/u;
|
|
145
|
+
const RE_HANGUL = /\p{Script=Hangul}/u;
|
|
146
|
+
|
|
147
|
+
/** Terminal display width: CJK/Hangul/fullwidth = 2 cols (matches typical monospace). */
|
|
148
|
+
function displayWidth(str) {
|
|
149
|
+
let w = 0;
|
|
150
|
+
for (const ch of str) {
|
|
151
|
+
const cp = ch.codePointAt(0);
|
|
152
|
+
if (RE_HAN.test(ch) || RE_HANGUL.test(ch)) w += 2;
|
|
153
|
+
else if (cp >= 0xff01 && cp <= 0xff60) w += 2; // fullwidth forms
|
|
154
|
+
else w += 1;
|
|
155
|
+
}
|
|
156
|
+
return w;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function centerBlock(lines, termCols) {
|
|
160
|
+
const widths = lines.map((l) => displayWidth(stripAnsi(l)));
|
|
161
|
+
const maxW = Math.max(...widths, 1);
|
|
162
|
+
const pad = termCols >= maxW + 4 ? Math.max(0, Math.floor((termCols - maxW) / 2)) : 2;
|
|
163
|
+
const prefix = " ".repeat(pad);
|
|
164
|
+
return lines.map((l) => prefix + l);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* @param {object} opts
|
|
169
|
+
* @param {string} opts.version
|
|
170
|
+
* @param {boolean} [opts.noColor]
|
|
171
|
+
* @param {boolean} [opts.compact] — fewer blank lines (e.g. interactive)
|
|
172
|
+
*/
|
|
173
|
+
export function printWelcomeBanner(opts) {
|
|
174
|
+
const { version, noColor = false, compact = false } = opts;
|
|
175
|
+
if (!bannerColorAllowed(noColor)) {
|
|
176
|
+
printPlainBanner({ version, compact });
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const rawLines = solidBlockLines();
|
|
181
|
+
const maxW = Math.max(...rawLines.map((l) => l.length));
|
|
182
|
+
const termCols = process.stdout.columns || 80;
|
|
183
|
+
const useTc = colorDepth() >= 24;
|
|
184
|
+
|
|
185
|
+
const painted = rawLines.map((line) => {
|
|
186
|
+
const padded = line.padEnd(maxW);
|
|
187
|
+
return useTc ? paintLineTruecolor(padded, maxW) : paintLineFallback(padded, maxW);
|
|
188
|
+
});
|
|
189
|
+
const centered = centerBlock(painted, termCols);
|
|
190
|
+
|
|
191
|
+
const nl = compact ? "\n" : "\n\n";
|
|
192
|
+
console.log(nl + centered.join("\n"));
|
|
193
|
+
|
|
194
|
+
const tag = "✦ Discover · Inspect · Call · 10,000+ capabilities · intelligent orchestration ✦";
|
|
195
|
+
const meta = dim(`v${version} · ${shortenPath(processCwd())}`);
|
|
196
|
+
const tagLine = centerLine(dim(cyan(tag)), termCols);
|
|
197
|
+
const metaLine = centerLine(meta, termCols);
|
|
198
|
+
|
|
199
|
+
console.log("\n" + tagLine + "\n" + metaLine + (compact ? "\n" : "\n\n"));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function centerLine(text, termCols) {
|
|
203
|
+
const len = displayWidth(stripAnsi(text));
|
|
204
|
+
const pad = termCols >= len + 4 ? Math.max(0, Math.floor((termCols - len) / 2)) : 2;
|
|
205
|
+
return " ".repeat(pad) + text;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function printPlainBanner({ version, compact }) {
|
|
209
|
+
const rawLines = solidBlockLines();
|
|
210
|
+
const termCols = process.stdout.columns || 80;
|
|
211
|
+
const centered = centerBlock(rawLines, termCols);
|
|
212
|
+
const nl = compact ? "\n" : "\n\n";
|
|
213
|
+
console.log(nl + centered.join("\n"));
|
|
214
|
+
const tag = "✦ Discover · Inspect · Call · 10,000+ capabilities · intelligent orchestration ✦";
|
|
215
|
+
const meta = `v${version} · ${shortenPath(processCwd())}`;
|
|
216
|
+
const tagLine = centerLine(dim(tag), termCols);
|
|
217
|
+
const metaLine = centerLine(dim(meta), termCols);
|
|
218
|
+
console.log("\n" + tagLine + "\n" + metaLine + (compact ? "\n" : "\n\n"));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Login screen: banner + framed hint (cyan border).
|
|
223
|
+
*/
|
|
224
|
+
export function printLoginBanner(opts) {
|
|
225
|
+
const { version, noColor = false } = opts;
|
|
226
|
+
printWelcomeBanner({ version, noColor, compact: true });
|
|
227
|
+
|
|
228
|
+
if (!bannerColorAllowed(noColor)) {
|
|
229
|
+
console.log(dim(" ─ Secure login · paste your API key below"));
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const termCols = process.stdout.columns || 80;
|
|
234
|
+
const inner = " Secure login · API key ";
|
|
235
|
+
const maxInner = Math.min(inner.length + 4, termCols - 4);
|
|
236
|
+
const bar = "─".repeat(Math.max(8, maxInner));
|
|
237
|
+
const top = centerLine(cyan("╭" + bar + "╮"), termCols);
|
|
238
|
+
const mid = centerLine(cyan("│") + dim(inner) + cyan("│"), termCols);
|
|
239
|
+
const bot = centerLine(cyan("╰" + bar + "╯"), termCols);
|
|
240
|
+
console.log(top + "\n" + mid + "\n" + bot + "\n");
|
|
241
|
+
}
|
package/src/output/colors.mjs
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
(
|
|
1
|
+
let memoizedEnabled;
|
|
2
|
+
export const isColorEnabled = () => {
|
|
3
|
+
if (memoizedEnabled !== undefined) return memoizedEnabled;
|
|
4
|
+
memoizedEnabled = !process.env.NO_COLOR &&
|
|
5
|
+
(process.env.FORCE_COLOR === "1" || (process.stdout.isTTY && process.stderr.isTTY));
|
|
6
|
+
return memoizedEnabled;
|
|
7
|
+
};
|
|
4
8
|
|
|
5
|
-
const code = (open, close) =>
|
|
6
|
-
|
|
9
|
+
const code = (open, close) => (s) =>
|
|
10
|
+
isColorEnabled() ? `\x1b[${open}m${s}\x1b[${close}m` : s;
|
|
7
11
|
|
|
8
12
|
export const bold = code("1", "22");
|
|
9
13
|
export const dim = code("2", "22");
|
|
@@ -12,5 +16,3 @@ export const green = code("32", "39");
|
|
|
12
16
|
export const yellow = code("33", "39");
|
|
13
17
|
export const cyan = code("36", "39");
|
|
14
18
|
export const gray = code("90", "39");
|
|
15
|
-
|
|
16
|
-
export const isColorEnabled = () => enabled;
|