@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 CHANGED
@@ -57,7 +57,7 @@ tool: weather_gov... · id: 7ebcaf9d-...
57
57
  **One-liner (recommended):**
58
58
 
59
59
  ```bash
60
- curl -fsSL https://qveris.ai/install | bash
60
+ curl -fsSL https://qveris.ai/cli/install | bash
61
61
  ```
62
62
 
63
63
  **Or via npm:**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qverisai/cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "QVeris CLI -- discover, inspect, and call 10,000+ capabilities",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bash
2
2
  # QVeris CLI Installer
3
- # Usage: curl -fsSL https://qveris.ai/install | bash
3
+ # Usage: curl -fsSL https://qveris.ai/cli/install | bash
4
4
  set -euo pipefail
5
5
 
6
6
  BOLD='\033[1m'
@@ -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
- console.log(`\n ${bold("QVeris Interactive Mode")}`);
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({
@@ -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
- console.log(`\n Get your API key at: ${cyan(ACCOUNT_URL)}\n`);
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(`v${VERSION}`)} -- discover, inspect, and call 10,000+ capabilities
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
+ }
@@ -1,9 +1,13 @@
1
- const enabled =
2
- !process.env.NO_COLOR &&
3
- (process.env.FORCE_COLOR === "1" || (process.stdout.isTTY && process.stderr.isTTY));
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
- enabled ? (s) => `\x1b[${open}m${s}\x1b[${close}m` : (s) => s;
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;