@qverisai/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +323 -0
- package/bin/qveris.mjs +7 -0
- package/package.json +35 -0
- package/scripts/install.sh +129 -0
- package/src/client/api.mjs +96 -0
- package/src/client/auth.mjs +26 -0
- package/src/commands/call.mjs +100 -0
- package/src/commands/completions.mjs +58 -0
- package/src/commands/config.mjs +96 -0
- package/src/commands/credits.mjs +32 -0
- package/src/commands/discover.mjs +48 -0
- package/src/commands/doctor.mjs +47 -0
- package/src/commands/history.mjs +47 -0
- package/src/commands/inspect.mjs +47 -0
- package/src/commands/interactive.mjs +171 -0
- package/src/commands/login.mjs +163 -0
- package/src/compat/aliases.mjs +30 -0
- package/src/config/defaults.mjs +16 -0
- package/src/config/resolve.mjs +32 -0
- package/src/config/store.mjs +47 -0
- package/src/errors/codes.mjs +55 -0
- package/src/errors/handler.mjs +35 -0
- package/src/main.mjs +250 -0
- package/src/output/codegen.mjs +75 -0
- package/src/output/colors.mjs +16 -0
- package/src/output/formatter.mjs +280 -0
- package/src/output/json.mjs +11 -0
- package/src/output/spinner.mjs +17 -0
- package/src/session/session.mjs +53 -0
- package/src/utils/params.mjs +35 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { createInterface } from "node:readline";
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { platform } from "node:os";
|
|
4
|
+
import { resolve } from "../config/resolve.mjs";
|
|
5
|
+
import { setConfigValue, deleteConfigValue } from "../config/store.mjs";
|
|
6
|
+
import { discoverTools } from "../client/api.mjs";
|
|
7
|
+
import { bold, green, red, dim, cyan } from "../output/colors.mjs";
|
|
8
|
+
|
|
9
|
+
const ACCOUNT_URL = "https://qveris.ai/account?page=api-keys";
|
|
10
|
+
|
|
11
|
+
function openBrowser(url) {
|
|
12
|
+
const cmds = { darwin: "open", win32: "cmd", linux: "xdg-open" };
|
|
13
|
+
const cmd = cmds[platform()] || "xdg-open";
|
|
14
|
+
const args = platform() === "win32" ? ["/c", "start", "", url] : [url];
|
|
15
|
+
execFile(cmd, args, () => {});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Masked input prompt using raw mode.
|
|
20
|
+
* Handles Backspace, Ctrl+C, paste (multi-char chunks), and echoes * per character.
|
|
21
|
+
*/
|
|
22
|
+
function prompt(question) {
|
|
23
|
+
return new Promise((pResolve, pReject) => {
|
|
24
|
+
process.stderr.write(question);
|
|
25
|
+
|
|
26
|
+
// Fallback for non-TTY (piped input): read without masking
|
|
27
|
+
if (!process.stdin.isTTY) {
|
|
28
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
29
|
+
rl.question("", (answer) => { rl.close(); pResolve(answer.trim()); });
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let buf = "";
|
|
34
|
+
|
|
35
|
+
const cleanup = () => {
|
|
36
|
+
process.stdin.removeListener("data", onData);
|
|
37
|
+
try { process.stdin.setRawMode(false); } catch { /* already restored */ }
|
|
38
|
+
process.stdin.pause();
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Safety net: restore terminal if process exits unexpectedly
|
|
42
|
+
const onExit = () => { try { process.stdin.setRawMode(false); } catch {} };
|
|
43
|
+
process.once("exit", onExit);
|
|
44
|
+
|
|
45
|
+
process.stdin.setRawMode(true);
|
|
46
|
+
process.stdin.resume();
|
|
47
|
+
process.stdin.setEncoding("utf-8");
|
|
48
|
+
|
|
49
|
+
// Process each character in the chunk individually to handle paste correctly
|
|
50
|
+
const onData = (chunk) => {
|
|
51
|
+
for (const char of chunk) {
|
|
52
|
+
// Ctrl+C
|
|
53
|
+
if (char === "\x03") {
|
|
54
|
+
cleanup();
|
|
55
|
+
process.removeListener("exit", onExit);
|
|
56
|
+
process.stderr.write("\n");
|
|
57
|
+
pReject(new Error("Aborted"));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
// Enter
|
|
61
|
+
if (char === "\r" || char === "\n") {
|
|
62
|
+
cleanup();
|
|
63
|
+
process.removeListener("exit", onExit);
|
|
64
|
+
process.stderr.write("\n");
|
|
65
|
+
pResolve(buf.trim());
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
// Backspace / Delete
|
|
69
|
+
if (char === "\x7f" || char === "\b") {
|
|
70
|
+
if (buf.length > 0) {
|
|
71
|
+
buf = buf.slice(0, -1);
|
|
72
|
+
process.stderr.write("\b \b");
|
|
73
|
+
}
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
// Printable character
|
|
77
|
+
if (char.charCodeAt(0) >= 32) {
|
|
78
|
+
buf += char;
|
|
79
|
+
process.stderr.write("*");
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
process.stdin.on("data", onData);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function runLogin(flags) {
|
|
89
|
+
if (flags.token) {
|
|
90
|
+
await validateAndSave(flags.token);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
console.log(`\n Get your API key at: ${cyan(ACCOUNT_URL)}\n`);
|
|
95
|
+
|
|
96
|
+
if (!flags.noBrowser) {
|
|
97
|
+
openBrowser(ACCOUNT_URL);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let key;
|
|
101
|
+
try {
|
|
102
|
+
key = await prompt(" Paste your API key: ");
|
|
103
|
+
} catch {
|
|
104
|
+
// Ctrl+C during prompt
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (!key) {
|
|
108
|
+
console.error(` ${red("\u2718")} No key provided.`);
|
|
109
|
+
process.exitCode = 1;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
await validateAndSave(key);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function validateAndSave(key) {
|
|
117
|
+
process.stderr.write(` Validating key...`);
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
await discoverTools({ apiKey: key, query: "test", limit: 1, timeoutMs: 10000 });
|
|
121
|
+
setConfigValue("api_key", key);
|
|
122
|
+
const masked = key.slice(0, 6) + "..." + key.slice(-4);
|
|
123
|
+
console.error(`\r\x1b[K`);
|
|
124
|
+
console.log(` ${green("\u2713")} Authenticated as ${bold(masked)}`);
|
|
125
|
+
console.log(` ${dim("Key saved to config.")}`);
|
|
126
|
+
} catch {
|
|
127
|
+
console.error(`\r\x1b[K`);
|
|
128
|
+
console.error(` ${red("\u2718")} Invalid API key. Please check and try again.`);
|
|
129
|
+
process.exitCode = 1;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export async function runLogout() {
|
|
134
|
+
deleteConfigValue("api_key");
|
|
135
|
+
console.log(` ${green("\u2713")} API key removed from config.`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export async function runWhoami(flags) {
|
|
139
|
+
const { value: key, source } = resolve("api_key", flags.apiKey);
|
|
140
|
+
|
|
141
|
+
if (!key) {
|
|
142
|
+
console.log(`\n Not authenticated. Run ${cyan("qveris login")} to set your API key.`);
|
|
143
|
+
process.exitCode = 1;
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const masked = key.slice(0, 6) + "..." + key.slice(-4);
|
|
148
|
+
|
|
149
|
+
process.stderr.write(` Validating...`);
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
await discoverTools({ apiKey: key, query: "test", limit: 1, timeoutMs: 10000 });
|
|
153
|
+
process.stderr.write("\r\x1b[K");
|
|
154
|
+
console.log(`\n ${green("\u2713")} Authenticated`);
|
|
155
|
+
console.log(` Key: ${bold(masked)}`);
|
|
156
|
+
console.log(` Source: ${dim(source)}`);
|
|
157
|
+
} catch {
|
|
158
|
+
console.error(`\r\x1b[K`);
|
|
159
|
+
console.log(`\n ${red("\u2718")} Key ${masked} is ${red("invalid")}`);
|
|
160
|
+
console.log(` Source: ${dim(source)}`);
|
|
161
|
+
process.exitCode = 1;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const COMMAND_ALIASES = {
|
|
2
|
+
search: "discover",
|
|
3
|
+
execute: "call",
|
|
4
|
+
invoke: "call",
|
|
5
|
+
"get-by-ids": "inspect",
|
|
6
|
+
repl: "interactive",
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const FLAG_ALIASES = {
|
|
10
|
+
"--search-id": "--discovery-id",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function normalizeLegacyArgs(args) {
|
|
14
|
+
const result = [...args];
|
|
15
|
+
const warnings = [];
|
|
16
|
+
|
|
17
|
+
if (result.length > 0 && COMMAND_ALIASES[result[0]]) {
|
|
18
|
+
warnings.push(`'${result[0]}' is deprecated; use '${COMMAND_ALIASES[result[0]]}' instead.`);
|
|
19
|
+
result[0] = COMMAND_ALIASES[result[0]];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
for (let i = 0; i < result.length; i++) {
|
|
23
|
+
if (FLAG_ALIASES[result[i]]) {
|
|
24
|
+
warnings.push(`'${result[i]}' is deprecated; use '${FLAG_ALIASES[result[i]]}' instead.`);
|
|
25
|
+
result[i] = FLAG_ALIASES[result[i]];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return { args: result, warnings };
|
|
30
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join, dirname } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, "../../package.json"), "utf-8"));
|
|
7
|
+
|
|
8
|
+
export const DEFAULTS = {
|
|
9
|
+
base_url: "https://qveris.ai/api/v1",
|
|
10
|
+
default_limit: 5,
|
|
11
|
+
default_max_size: 4096,
|
|
12
|
+
color: "auto",
|
|
13
|
+
output_format: "human",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const VERSION = pkg.version;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { readConfig } from "./store.mjs";
|
|
2
|
+
import { DEFAULTS } from "./defaults.mjs";
|
|
3
|
+
|
|
4
|
+
const ENV_MAP = {
|
|
5
|
+
api_key: "QVERIS_API_KEY",
|
|
6
|
+
base_url: "QVERIS_BASE_URL",
|
|
7
|
+
default_limit: "QVERIS_DEFAULT_LIMIT",
|
|
8
|
+
default_max_size: "QVERIS_DEFAULT_MAX_SIZE",
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function resolve(key, flagValue) {
|
|
12
|
+
if (flagValue !== undefined && flagValue !== null) return { value: flagValue, source: "flag" };
|
|
13
|
+
|
|
14
|
+
const envVar = ENV_MAP[key];
|
|
15
|
+
if (envVar && process.env[envVar]) return { value: process.env[envVar], source: `env (${envVar})` };
|
|
16
|
+
|
|
17
|
+
const config = readConfig();
|
|
18
|
+
if (config[key] !== undefined) return { value: config[key], source: "config" };
|
|
19
|
+
|
|
20
|
+
if (DEFAULTS[key] !== undefined) return { value: DEFAULTS[key], source: "default" };
|
|
21
|
+
|
|
22
|
+
return { value: undefined, source: "none" };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function resolveAll(flags = {}) {
|
|
26
|
+
const keys = new Set([...Object.keys(DEFAULTS), ...Object.keys(ENV_MAP), ...Object.keys(readConfig())]);
|
|
27
|
+
const result = {};
|
|
28
|
+
for (const key of keys) {
|
|
29
|
+
result[key] = resolve(key, flags[key]);
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
|
|
5
|
+
function configDir() {
|
|
6
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
7
|
+
const base = xdg || join(homedir(), ".config");
|
|
8
|
+
return join(base, "qveris");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function configPath() {
|
|
12
|
+
return join(configDir(), "config.json");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function getConfigPath() {
|
|
16
|
+
return configPath();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function readConfig() {
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(readFileSync(configPath(), "utf-8"));
|
|
22
|
+
} catch {
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function writeConfig(config) {
|
|
28
|
+
const dir = configDir();
|
|
29
|
+
mkdirSync(dir, { recursive: true });
|
|
30
|
+
writeFileSync(configPath(), JSON.stringify(config, null, 2) + "\n", { mode: 0o600 });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function getConfigValue(key) {
|
|
34
|
+
return readConfig()[key];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function setConfigValue(key, value) {
|
|
38
|
+
const config = readConfig();
|
|
39
|
+
config[key] = value;
|
|
40
|
+
writeConfig(config);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function deleteConfigValue(key) {
|
|
44
|
+
const config = readConfig();
|
|
45
|
+
delete config[key];
|
|
46
|
+
writeConfig(config);
|
|
47
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export const EX_OK = 0;
|
|
2
|
+
export const EX_GENERAL = 1;
|
|
3
|
+
export const EX_USAGE = 2;
|
|
4
|
+
export const EX_UNAVAILABLE = 69;
|
|
5
|
+
export const EX_TEMPFAIL = 75;
|
|
6
|
+
export const EX_NOPERM = 77;
|
|
7
|
+
export const EX_CONFIG = 78;
|
|
8
|
+
|
|
9
|
+
export const ERROR_CODES = {
|
|
10
|
+
AUTH_MISSING_KEY: {
|
|
11
|
+
message: "No API key configured",
|
|
12
|
+
hint: "Run 'qveris login' or set QVERIS_API_KEY",
|
|
13
|
+
exit: EX_CONFIG,
|
|
14
|
+
},
|
|
15
|
+
AUTH_INVALID_KEY: {
|
|
16
|
+
message: "Authentication failed",
|
|
17
|
+
hint: "Check your key at https://qveris.ai/account",
|
|
18
|
+
exit: EX_NOPERM,
|
|
19
|
+
},
|
|
20
|
+
NET_TIMEOUT: {
|
|
21
|
+
message: "Request timed out",
|
|
22
|
+
hint: "Check connectivity or increase --timeout",
|
|
23
|
+
exit: EX_TEMPFAIL,
|
|
24
|
+
},
|
|
25
|
+
API_ERROR: {
|
|
26
|
+
message: "API error",
|
|
27
|
+
hint: null,
|
|
28
|
+
exit: EX_GENERAL,
|
|
29
|
+
},
|
|
30
|
+
PARAMS_INVALID_JSON: {
|
|
31
|
+
message: "Invalid JSON in --params",
|
|
32
|
+
hint: "Check JSON syntax in --params value",
|
|
33
|
+
exit: EX_USAGE,
|
|
34
|
+
},
|
|
35
|
+
TOOL_NOT_FOUND: {
|
|
36
|
+
message: "Tool not found",
|
|
37
|
+
hint: "Run 'qveris discover' to find available tools",
|
|
38
|
+
exit: EX_USAGE,
|
|
39
|
+
},
|
|
40
|
+
RATE_LIMITED: {
|
|
41
|
+
message: "Rate limited",
|
|
42
|
+
hint: "Wait and retry, or upgrade your plan",
|
|
43
|
+
exit: EX_TEMPFAIL,
|
|
44
|
+
},
|
|
45
|
+
CREDITS_INSUFFICIENT: {
|
|
46
|
+
message: "Insufficient credits",
|
|
47
|
+
hint: "Purchase credits at https://qveris.ai/pricing",
|
|
48
|
+
exit: EX_NOPERM,
|
|
49
|
+
},
|
|
50
|
+
SESSION_EXPIRED: {
|
|
51
|
+
message: "Session expired",
|
|
52
|
+
hint: "Run 'qveris discover' to start a new session",
|
|
53
|
+
exit: EX_USAGE,
|
|
54
|
+
},
|
|
55
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { red, bold, dim } from "../output/colors.mjs";
|
|
2
|
+
import { outputJsonError } from "../output/json.mjs";
|
|
3
|
+
import { ERROR_CODES, EX_GENERAL, EX_TEMPFAIL, EX_NOPERM } from "./codes.mjs";
|
|
4
|
+
|
|
5
|
+
export class CliError extends Error {
|
|
6
|
+
constructor(code, detail) {
|
|
7
|
+
const template = ERROR_CODES[code] || { message: code, hint: null, exit: EX_GENERAL };
|
|
8
|
+
super(detail || template.message);
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.hint = template.hint;
|
|
11
|
+
this.exitCode = template.exit;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function handleError(err, jsonMode = false) {
|
|
16
|
+
const exitCode = err.exitCode || classifyHttpError(err) || EX_GENERAL;
|
|
17
|
+
|
|
18
|
+
if (jsonMode) {
|
|
19
|
+
outputJsonError(err, exitCode);
|
|
20
|
+
} else {
|
|
21
|
+
console.error(`\n ${red("\u2718")} ${bold("Error:")} ${err.message}`);
|
|
22
|
+
const hint = err.hint || ERROR_CODES[err.code]?.hint;
|
|
23
|
+
if (hint) console.error(` ${dim(hint)}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
process.exitCode = exitCode;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function classifyHttpError(err) {
|
|
30
|
+
const msg = err.message || "";
|
|
31
|
+
if (err.name === "AbortError" || msg.includes("abort")) return EX_TEMPFAIL;
|
|
32
|
+
if (msg.includes("401") || msg.includes("403")) return EX_NOPERM;
|
|
33
|
+
if (msg.includes("429")) return EX_TEMPFAIL;
|
|
34
|
+
return null;
|
|
35
|
+
}
|
package/src/main.mjs
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { normalizeLegacyArgs } from "./compat/aliases.mjs";
|
|
2
|
+
import { handleError } from "./errors/handler.mjs";
|
|
3
|
+
import { VERSION } from "./config/defaults.mjs";
|
|
4
|
+
import { bold, dim, cyan } from "./output/colors.mjs";
|
|
5
|
+
|
|
6
|
+
export async function main(argv) {
|
|
7
|
+
const rawArgs = argv.slice(2);
|
|
8
|
+
const { args, warnings } = normalizeLegacyArgs(rawArgs);
|
|
9
|
+
|
|
10
|
+
for (const w of warnings) {
|
|
11
|
+
console.error(` ${dim("Deprecated:")} ${w}`);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const flags = extractGlobalFlags(args);
|
|
15
|
+
const positional = flags._positional;
|
|
16
|
+
const command = positional[0];
|
|
17
|
+
const rest = positional.slice(1);
|
|
18
|
+
|
|
19
|
+
if (flags.version) {
|
|
20
|
+
console.log(`qveris/${VERSION}`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!command || flags.help) {
|
|
25
|
+
printUsage();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
switch (command) {
|
|
31
|
+
case "discover": {
|
|
32
|
+
const query = rest.join(" ");
|
|
33
|
+
if (!query) { console.error(" Usage: qveris discover <query>"); process.exitCode = 2; return; }
|
|
34
|
+
const { runDiscover } = await import("./commands/discover.mjs");
|
|
35
|
+
await runDiscover(query, flags);
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
case "inspect": {
|
|
39
|
+
if (rest.length === 0) { console.error(" Usage: qveris inspect <tool_id|index> [...]"); process.exitCode = 2; return; }
|
|
40
|
+
const { runInspect } = await import("./commands/inspect.mjs");
|
|
41
|
+
await runInspect(rest, flags);
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
case "call": {
|
|
45
|
+
if (rest.length === 0) { console.error(" Usage: qveris call <tool_id|index> [--params <json>]"); process.exitCode = 2; return; }
|
|
46
|
+
const { runCall } = await import("./commands/call.mjs");
|
|
47
|
+
await runCall(rest[0], flags);
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
case "login": {
|
|
51
|
+
const { runLogin } = await import("./commands/login.mjs");
|
|
52
|
+
await runLogin(flags);
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
case "logout": {
|
|
56
|
+
const { runLogout } = await import("./commands/login.mjs");
|
|
57
|
+
await runLogout();
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
case "whoami": {
|
|
61
|
+
const { runWhoami } = await import("./commands/login.mjs");
|
|
62
|
+
await runWhoami(flags);
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
case "credits": {
|
|
66
|
+
const { runCredits } = await import("./commands/credits.mjs");
|
|
67
|
+
await runCredits(flags);
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
case "config": {
|
|
71
|
+
const subcommand = rest[0];
|
|
72
|
+
const subArgs = rest.slice(1);
|
|
73
|
+
if (!subcommand) { console.error(" Usage: qveris config <set|get|list|reset|path>"); process.exitCode = 2; return; }
|
|
74
|
+
const { runConfig } = await import("./commands/config.mjs");
|
|
75
|
+
await runConfig(subcommand, subArgs, flags);
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
case "interactive":
|
|
79
|
+
case "repl": {
|
|
80
|
+
const { runInteractive } = await import("./commands/interactive.mjs");
|
|
81
|
+
await runInteractive(flags);
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
case "doctor": {
|
|
85
|
+
const { runDoctor } = await import("./commands/doctor.mjs");
|
|
86
|
+
await runDoctor(flags);
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
case "history": {
|
|
90
|
+
const { runHistory } = await import("./commands/history.mjs");
|
|
91
|
+
await runHistory(flags);
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
case "completions": {
|
|
95
|
+
const { runCompletions } = await import("./commands/completions.mjs");
|
|
96
|
+
await runCompletions(rest[0]);
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
default:
|
|
100
|
+
console.error(` Unknown command: ${command}`);
|
|
101
|
+
console.error(` Run ${cyan("qveris --help")} for available commands.`);
|
|
102
|
+
process.exitCode = 2;
|
|
103
|
+
}
|
|
104
|
+
} catch (err) {
|
|
105
|
+
handleError(err, flags.json);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Boolean short flags: -j, -v, -V, -h
|
|
110
|
+
const SHORT_BOOLS = { j: "json", v: "verbose", V: "version", h: "help" };
|
|
111
|
+
|
|
112
|
+
// --key=value long flag mapping (flag name -> flags property)
|
|
113
|
+
const VALUE_FLAGS = {
|
|
114
|
+
"api-key": "apiKey", "base-url": "baseUrl", timeout: "timeout",
|
|
115
|
+
limit: "limit", "discovery-id": "discoveryId", params: "params",
|
|
116
|
+
"max-size": "maxSize", codegen: "codegen", token: "token",
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
function takeNext(args, i, flag) {
|
|
120
|
+
const next = args[i + 1];
|
|
121
|
+
if (next === undefined || next.startsWith("--")) {
|
|
122
|
+
console.error(` Error: ${flag} requires a value`);
|
|
123
|
+
process.exitCode = 2;
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
return next;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function extractGlobalFlags(args) {
|
|
130
|
+
const flags = { _positional: [] };
|
|
131
|
+
|
|
132
|
+
for (let i = 0; i < args.length; i++) {
|
|
133
|
+
let arg = args[i];
|
|
134
|
+
|
|
135
|
+
// -- signals end of options; everything after is positional
|
|
136
|
+
if (arg === "--") {
|
|
137
|
+
flags._positional.push(...args.slice(i + 1));
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Handle --key=value syntax
|
|
142
|
+
if (arg.startsWith("--") && arg.includes("=")) {
|
|
143
|
+
const eqIdx = arg.indexOf("=");
|
|
144
|
+
const key = arg.slice(2, eqIdx);
|
|
145
|
+
const val = arg.slice(eqIdx + 1);
|
|
146
|
+
if (VALUE_FLAGS[key]) { flags[VALUE_FLAGS[key]] = val; continue; }
|
|
147
|
+
// Boolean flags with =value (unusual but handle gracefully)
|
|
148
|
+
if (key === "json") { flags.json = true; continue; }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Handle combined short flags: -jv → -j + -v
|
|
152
|
+
if (arg.startsWith("-") && !arg.startsWith("--") && arg.length > 2) {
|
|
153
|
+
const chars = arg.slice(1);
|
|
154
|
+
let allBool = true;
|
|
155
|
+
for (const ch of chars) { if (!SHORT_BOOLS[ch]) { allBool = false; break; } }
|
|
156
|
+
if (allBool) {
|
|
157
|
+
for (const ch of chars) flags[SHORT_BOOLS[ch]] = true;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
switch (arg) {
|
|
163
|
+
case "--json": case "-j":
|
|
164
|
+
flags.json = true; break;
|
|
165
|
+
case "--no-color":
|
|
166
|
+
flags.noColor = true; break;
|
|
167
|
+
case "--verbose": case "-v":
|
|
168
|
+
flags.verbose = true; break;
|
|
169
|
+
case "--help": case "-h":
|
|
170
|
+
flags.help = true; break;
|
|
171
|
+
case "--version": case "-V":
|
|
172
|
+
flags.version = true; break;
|
|
173
|
+
case "--dry-run":
|
|
174
|
+
flags.dryRun = true; break;
|
|
175
|
+
case "--no-browser":
|
|
176
|
+
flags.noBrowser = true; break;
|
|
177
|
+
case "--clear":
|
|
178
|
+
flags.clear = true; break;
|
|
179
|
+
case "--api-key":
|
|
180
|
+
flags.apiKey = takeNext(args, i++, arg); break;
|
|
181
|
+
case "--base-url":
|
|
182
|
+
flags.baseUrl = takeNext(args, i++, arg); break;
|
|
183
|
+
case "--timeout":
|
|
184
|
+
flags.timeout = takeNext(args, i++, arg); break;
|
|
185
|
+
case "--limit":
|
|
186
|
+
flags.limit = takeNext(args, i++, arg); break;
|
|
187
|
+
case "--discovery-id":
|
|
188
|
+
flags.discoveryId = takeNext(args, i++, arg); break;
|
|
189
|
+
case "--params":
|
|
190
|
+
flags.params = takeNext(args, i++, arg); break;
|
|
191
|
+
case "--max-size":
|
|
192
|
+
flags.maxSize = takeNext(args, i++, arg); break;
|
|
193
|
+
case "--codegen":
|
|
194
|
+
flags.codegen = takeNext(args, i++, arg); break;
|
|
195
|
+
case "--token":
|
|
196
|
+
flags.token = takeNext(args, i++, arg); break;
|
|
197
|
+
default:
|
|
198
|
+
flags._positional.push(arg);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return flags;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function printUsage() {
|
|
206
|
+
console.log(`
|
|
207
|
+
${bold("QVeris CLI")} ${dim(`v${VERSION}`)} -- discover, inspect, and call 10,000+ capabilities
|
|
208
|
+
|
|
209
|
+
${bold("Usage:")}
|
|
210
|
+
qveris <command> [args] [flags]
|
|
211
|
+
|
|
212
|
+
${bold("Core Commands:")}
|
|
213
|
+
${cyan("discover")} <query> Find capabilities by natural language
|
|
214
|
+
${cyan("inspect")} <tool_id|index> View tool details, parameters, and stats
|
|
215
|
+
${cyan("call")} <tool_id|index> Execute a capability
|
|
216
|
+
|
|
217
|
+
${bold("Account:")}
|
|
218
|
+
${cyan("login")} Authenticate with QVeris
|
|
219
|
+
${cyan("logout")} Remove stored API key
|
|
220
|
+
${cyan("whoami")} Show current auth status
|
|
221
|
+
${cyan("credits")} Show credit balance
|
|
222
|
+
|
|
223
|
+
${bold("Configuration:")}
|
|
224
|
+
${cyan("config")} set|get|list|reset Manage CLI settings
|
|
225
|
+
|
|
226
|
+
${bold("Utilities:")}
|
|
227
|
+
${cyan("interactive")} Launch interactive REPL mode
|
|
228
|
+
${cyan("history")} Show current session
|
|
229
|
+
${cyan("doctor")} Self-check diagnostics
|
|
230
|
+
${cyan("completions")} <shell> Generate shell completions
|
|
231
|
+
|
|
232
|
+
${bold("Global Flags:")}
|
|
233
|
+
--json, -j Output raw JSON
|
|
234
|
+
--api-key <key> Override API key
|
|
235
|
+
--timeout <seconds> Request timeout
|
|
236
|
+
--no-color Disable colors
|
|
237
|
+
--verbose, -v Show request details
|
|
238
|
+
--version, -V Print version
|
|
239
|
+
--help, -h Show help
|
|
240
|
+
|
|
241
|
+
${bold("Examples:")}
|
|
242
|
+
qveris discover "weather forecast API"
|
|
243
|
+
qveris inspect 1
|
|
244
|
+
qveris call 1 --params '{"city": "London"}'
|
|
245
|
+
qveris call 1 --params @params.json --codegen curl
|
|
246
|
+
qveris interactive
|
|
247
|
+
|
|
248
|
+
${dim("https://qveris.ai")}
|
|
249
|
+
`);
|
|
250
|
+
}
|