@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,100 @@
|
|
|
1
|
+
import { resolveApiKey } from "../client/auth.mjs";
|
|
2
|
+
import { callTool } from "../client/api.mjs";
|
|
3
|
+
import { resolveToolId, getSessionDiscoveryId } from "../session/session.mjs";
|
|
4
|
+
import { resolveParams } from "../utils/params.mjs";
|
|
5
|
+
import { formatCallResult } from "../output/formatter.mjs";
|
|
6
|
+
import { outputJson } from "../output/json.mjs";
|
|
7
|
+
import { createSpinner } from "../output/spinner.mjs";
|
|
8
|
+
import { generateSnippet } from "../output/codegen.mjs";
|
|
9
|
+
import { CliError } from "../errors/handler.mjs";
|
|
10
|
+
import { bold, dim, cyan } from "../output/colors.mjs";
|
|
11
|
+
|
|
12
|
+
// Smart max_response_size defaults:
|
|
13
|
+
// --max-size N → user explicit override (highest priority)
|
|
14
|
+
// --json → 20480 (agent/LLM scenario, matches MCP server)
|
|
15
|
+
// non-TTY → 20480 (piped/scripted, likely agent)
|
|
16
|
+
// TTY → 4096 (human terminal, auto-truncate large results)
|
|
17
|
+
const MAX_SIZE_TTY = 4096;
|
|
18
|
+
const MAX_SIZE_AGENT = 20480;
|
|
19
|
+
|
|
20
|
+
function resolveMaxSize(flags) {
|
|
21
|
+
if (flags.maxSize !== undefined) {
|
|
22
|
+
const parsed = parseInt(flags.maxSize, 10);
|
|
23
|
+
if (isNaN(parsed)) throw new CliError("API_ERROR", "Invalid --max-size: must be an integer");
|
|
24
|
+
return parsed;
|
|
25
|
+
}
|
|
26
|
+
if (flags.json) return MAX_SIZE_AGENT;
|
|
27
|
+
if (!process.stdout.isTTY) return MAX_SIZE_AGENT;
|
|
28
|
+
return MAX_SIZE_TTY;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function runCall(idOrIndex, flags) {
|
|
32
|
+
const apiKey = resolveApiKey(flags.apiKey);
|
|
33
|
+
const timeoutMs = (parseInt(flags.timeout, 10) || 60) * 1000;
|
|
34
|
+
const maxSize = resolveMaxSize(flags);
|
|
35
|
+
|
|
36
|
+
const resolved = resolveToolId(idOrIndex);
|
|
37
|
+
const toolId = resolved.toolId;
|
|
38
|
+
let discoveryId = flags.discoveryId || null;
|
|
39
|
+
|
|
40
|
+
if (!discoveryId && resolved.fromSession && resolved.discoveryId) {
|
|
41
|
+
discoveryId = resolved.discoveryId;
|
|
42
|
+
}
|
|
43
|
+
if (!discoveryId) discoveryId = getSessionDiscoveryId();
|
|
44
|
+
if (!discoveryId && /^\d+$/.test(idOrIndex)) {
|
|
45
|
+
throw new CliError("SESSION_EXPIRED", "No discovery ID. Run 'qveris discover' first or pass --discovery-id.");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const parameters = resolveParams(flags.params || "{}");
|
|
49
|
+
|
|
50
|
+
if (flags.dryRun) {
|
|
51
|
+
if (flags.json) {
|
|
52
|
+
outputJson({ dry_run: true, tool_id: toolId, discovery_id: discoveryId, parameters, max_response_size: maxSize });
|
|
53
|
+
} else {
|
|
54
|
+
console.log(`\n ${bold("Dry run")} -- would send:\n`);
|
|
55
|
+
console.log(` Tool: ${cyan(toolId)}`);
|
|
56
|
+
console.log(` Discovery ID: ${dim(discoveryId)}`);
|
|
57
|
+
console.log(` Max size: ${maxSize}`);
|
|
58
|
+
console.log(` Parameters:`);
|
|
59
|
+
console.log(JSON.stringify(parameters, null, 2).split("\n").map((l) => ` ${l}`).join("\n"));
|
|
60
|
+
}
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const spinner = flags.json ? { stop() {} } : createSpinner("Calling tool...");
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const result = await callTool({
|
|
68
|
+
apiKey,
|
|
69
|
+
baseUrl: flags.baseUrl,
|
|
70
|
+
toolId,
|
|
71
|
+
discoveryId,
|
|
72
|
+
parameters,
|
|
73
|
+
maxResponseSize: maxSize,
|
|
74
|
+
timeoutMs,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
spinner.stop();
|
|
78
|
+
|
|
79
|
+
if (flags.json) {
|
|
80
|
+
outputJson(result);
|
|
81
|
+
} else {
|
|
82
|
+
console.log(formatCallResult(result));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (flags.codegen && result.success) {
|
|
86
|
+
const snippet = generateSnippet(flags.codegen, {
|
|
87
|
+
toolId,
|
|
88
|
+
discoveryId,
|
|
89
|
+
parameters,
|
|
90
|
+
maxResponseSize: maxSize,
|
|
91
|
+
});
|
|
92
|
+
console.log(`\n ${dim("--- Code snippet (" + flags.codegen + ") ---")}\n`);
|
|
93
|
+
console.log(snippet);
|
|
94
|
+
console.log();
|
|
95
|
+
}
|
|
96
|
+
} catch (err) {
|
|
97
|
+
spinner.stop();
|
|
98
|
+
throw err;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const COMMANDS = [
|
|
2
|
+
"discover", "inspect", "call",
|
|
3
|
+
"login", "logout", "whoami", "credits",
|
|
4
|
+
"config", "interactive", "history", "doctor", "completions",
|
|
5
|
+
];
|
|
6
|
+
|
|
7
|
+
export async function runCompletions(shell) {
|
|
8
|
+
switch (shell) {
|
|
9
|
+
case "bash":
|
|
10
|
+
console.log(bashCompletions());
|
|
11
|
+
break;
|
|
12
|
+
case "zsh":
|
|
13
|
+
console.log(zshCompletions());
|
|
14
|
+
break;
|
|
15
|
+
case "fish":
|
|
16
|
+
console.log(fishCompletions());
|
|
17
|
+
break;
|
|
18
|
+
default:
|
|
19
|
+
console.error(` Supported shells: bash, zsh, fish`);
|
|
20
|
+
console.error(` Usage: eval "$(qveris completions bash)"`);
|
|
21
|
+
process.exitCode = 2;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function bashCompletions() {
|
|
26
|
+
return `# bash completion for qveris
|
|
27
|
+
_qveris_completions() {
|
|
28
|
+
local cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
29
|
+
local commands="${COMMANDS.join(" ")}"
|
|
30
|
+
if [ "\${COMP_CWORD}" -eq 1 ]; then
|
|
31
|
+
COMPREPLY=( $(compgen -W "\${commands}" -- "\${cur}") )
|
|
32
|
+
fi
|
|
33
|
+
}
|
|
34
|
+
complete -F _qveris_completions qveris`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function zshCompletions() {
|
|
38
|
+
return `# zsh completion for qveris
|
|
39
|
+
#compdef qveris
|
|
40
|
+
|
|
41
|
+
_qveris() {
|
|
42
|
+
local -a commands
|
|
43
|
+
commands=(
|
|
44
|
+
${COMMANDS.map((c) => ` '${c}:${c} command'`).join("\n")}
|
|
45
|
+
)
|
|
46
|
+
_arguments '1:command:->cmds' '*::arg:->args'
|
|
47
|
+
case "$state" in
|
|
48
|
+
cmds) _describe 'command' commands ;;
|
|
49
|
+
esac
|
|
50
|
+
}
|
|
51
|
+
_qveris`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function fishCompletions() {
|
|
55
|
+
return COMMANDS.map(
|
|
56
|
+
(c) => `complete -c qveris -n '__fish_use_subcommand' -a '${c}' -d '${c} command'`
|
|
57
|
+
).join("\n");
|
|
58
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { getConfigPath, getConfigValue, setConfigValue, writeConfig } from "../config/store.mjs";
|
|
2
|
+
import { resolveAll } from "../config/resolve.mjs";
|
|
3
|
+
import { bold, dim, cyan } from "../output/colors.mjs";
|
|
4
|
+
import { outputJson } from "../output/json.mjs";
|
|
5
|
+
|
|
6
|
+
const ALLOWED_KEYS = ["api_key", "base_url", "default_limit", "default_max_size", "color", "output_format"];
|
|
7
|
+
|
|
8
|
+
export async function runConfig(subcommand, args, flags) {
|
|
9
|
+
switch (subcommand) {
|
|
10
|
+
case "set": return configSet(args[0], args[1], flags);
|
|
11
|
+
case "get": return configGet(args[0], flags);
|
|
12
|
+
case "list": return configList(flags);
|
|
13
|
+
case "reset": return configReset(flags);
|
|
14
|
+
case "path": return configPath(flags);
|
|
15
|
+
default:
|
|
16
|
+
console.error(` Unknown config subcommand: ${subcommand}`);
|
|
17
|
+
console.error(` Usage: qveris config <set|get|list|reset|path>`);
|
|
18
|
+
process.exitCode = 2;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function configSet(key, value, flags) {
|
|
23
|
+
if (!key || value === undefined) {
|
|
24
|
+
console.error(" Usage: qveris config set <key> <value>");
|
|
25
|
+
process.exitCode = 2;
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (!ALLOWED_KEYS.includes(key)) {
|
|
29
|
+
console.error(` Unknown config key: ${key}`);
|
|
30
|
+
console.error(` Allowed: ${ALLOWED_KEYS.join(", ")}`);
|
|
31
|
+
process.exitCode = 2;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
let parsed = value;
|
|
35
|
+
if (key.endsWith("limit") || key.endsWith("size")) {
|
|
36
|
+
parsed = parseInt(value, 10);
|
|
37
|
+
if (isNaN(parsed)) {
|
|
38
|
+
console.error(` Error: ${key} must be a valid integer.`);
|
|
39
|
+
process.exitCode = 2;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
setConfigValue(key, parsed);
|
|
44
|
+
if (flags.json) {
|
|
45
|
+
outputJson({ key, value: parsed });
|
|
46
|
+
} else {
|
|
47
|
+
console.log(` ${key} = ${bold(String(parsed))}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function configGet(key, flags) {
|
|
52
|
+
if (!key) {
|
|
53
|
+
console.error(" Usage: qveris config get <key>");
|
|
54
|
+
process.exitCode = 2;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const val = getConfigValue(key);
|
|
58
|
+
if (flags.json) {
|
|
59
|
+
outputJson({ key, value: val ?? null });
|
|
60
|
+
} else {
|
|
61
|
+
console.log(val !== undefined ? ` ${key} = ${bold(String(val))}` : ` ${key} is not set`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function configList(flags) {
|
|
66
|
+
const all = resolveAll();
|
|
67
|
+
if (flags.json) {
|
|
68
|
+
const obj = {};
|
|
69
|
+
for (const [k, v] of Object.entries(all)) {
|
|
70
|
+
obj[k] = { value: k === "api_key" && v.value ? mask(v.value) : v.value, source: v.source };
|
|
71
|
+
}
|
|
72
|
+
outputJson(obj);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
console.log(`\n ${bold("Key")} ${bold("Value")} ${bold("Source")}`);
|
|
77
|
+
for (const [key, { value, source }] of Object.entries(all)) {
|
|
78
|
+
const display = key === "api_key" && value ? mask(value) : String(value ?? dim("(not set)"));
|
|
79
|
+
console.log(` ${cyan(key.padEnd(20))}${display.padEnd(25)}${dim(source)}`);
|
|
80
|
+
}
|
|
81
|
+
console.log();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function configReset() {
|
|
85
|
+
writeConfig({});
|
|
86
|
+
console.log(" Config reset to defaults.");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function configPath() {
|
|
90
|
+
console.log(getConfigPath());
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function mask(key) {
|
|
94
|
+
if (typeof key !== "string" || key.length < 10) return "***";
|
|
95
|
+
return key.slice(0, 6) + "..." + key.slice(-4);
|
|
96
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { resolveApiKey } from "../client/auth.mjs";
|
|
2
|
+
import { discoverTools } from "../client/api.mjs";
|
|
3
|
+
import { bold, dim, cyan, yellow, green } from "../output/colors.mjs";
|
|
4
|
+
import { outputJson } from "../output/json.mjs";
|
|
5
|
+
import { createSpinner } from "../output/spinner.mjs";
|
|
6
|
+
|
|
7
|
+
export async function runCredits(flags) {
|
|
8
|
+
const apiKey = resolveApiKey(flags.apiKey);
|
|
9
|
+
|
|
10
|
+
const spinner = flags.json ? { stop() {} } : createSpinner("Checking credits...");
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
// The backend returns remaining_credits in every /search response
|
|
14
|
+
const result = await discoverTools({ apiKey, baseUrl: flags.baseUrl, query: "credit balance", limit: 1, timeoutMs: 10000 });
|
|
15
|
+
spinner.stop();
|
|
16
|
+
|
|
17
|
+
const credits = result.remaining_credits;
|
|
18
|
+
|
|
19
|
+
if (flags.json) {
|
|
20
|
+
outputJson({ remaining_credits: credits ?? null });
|
|
21
|
+
} else if (credits !== undefined && credits !== null) {
|
|
22
|
+
console.log(`\n ${green("\u2713")} Credits remaining: ${bold(yellow(String(credits)))}`);
|
|
23
|
+
console.log(` ${dim("Manage at:")} ${cyan("https://qveris.ai/account")}\n`);
|
|
24
|
+
} else {
|
|
25
|
+
console.log(`\n ${dim("Credit balance not available in API response.")}`);
|
|
26
|
+
console.log(` Check at: ${cyan("https://qveris.ai/account")}\n`);
|
|
27
|
+
}
|
|
28
|
+
} catch (err) {
|
|
29
|
+
spinner.stop();
|
|
30
|
+
throw err;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { resolveApiKey } from "../client/auth.mjs";
|
|
2
|
+
import { discoverTools } from "../client/api.mjs";
|
|
3
|
+
import { writeSession } from "../session/session.mjs";
|
|
4
|
+
import { formatDiscoverResult } from "../output/formatter.mjs";
|
|
5
|
+
import { outputJson } from "../output/json.mjs";
|
|
6
|
+
import { createSpinner } from "../output/spinner.mjs";
|
|
7
|
+
|
|
8
|
+
export async function runDiscover(query, flags) {
|
|
9
|
+
const apiKey = resolveApiKey(flags.apiKey);
|
|
10
|
+
const limit = parseInt(flags.limit, 10) || 5;
|
|
11
|
+
const timeoutMs = (parseInt(flags.timeout, 10) || 30) * 1000;
|
|
12
|
+
|
|
13
|
+
const spinner = flags.json ? { stop() {} } : createSpinner("Discovering capabilities...");
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const result = await discoverTools({
|
|
17
|
+
apiKey,
|
|
18
|
+
baseUrl: flags.baseUrl,
|
|
19
|
+
query,
|
|
20
|
+
limit,
|
|
21
|
+
timeoutMs,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
spinner.stop();
|
|
25
|
+
|
|
26
|
+
// Store richer session data for index resolution
|
|
27
|
+
const tools = result.results ?? [];
|
|
28
|
+
writeSession({
|
|
29
|
+
discoveryId: result.search_id,
|
|
30
|
+
query,
|
|
31
|
+
results: tools.map((t, i) => ({
|
|
32
|
+
index: i + 1,
|
|
33
|
+
tool_id: t.tool_id,
|
|
34
|
+
name: t.name,
|
|
35
|
+
provider_name: t.provider_name,
|
|
36
|
+
})),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (flags.json) {
|
|
40
|
+
outputJson(result);
|
|
41
|
+
} else {
|
|
42
|
+
console.log(formatDiscoverResult(result));
|
|
43
|
+
}
|
|
44
|
+
} catch (err) {
|
|
45
|
+
spinner.stop();
|
|
46
|
+
throw err;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { resolve } from "../config/resolve.mjs";
|
|
2
|
+
import { discoverTools } from "../client/api.mjs";
|
|
3
|
+
import { bold, green, red, dim, cyan } from "../output/colors.mjs";
|
|
4
|
+
|
|
5
|
+
export async function runDoctor(flags) {
|
|
6
|
+
console.log(`\n ${bold("QVeris CLI Doctor")}\n`);
|
|
7
|
+
let allOk = true;
|
|
8
|
+
|
|
9
|
+
// Check Node.js version
|
|
10
|
+
const nodeVersion = process.version;
|
|
11
|
+
const major = parseInt(nodeVersion.slice(1), 10);
|
|
12
|
+
if (major >= 18) {
|
|
13
|
+
console.log(` ${green("\u2713")} Node.js ${nodeVersion}`);
|
|
14
|
+
} else {
|
|
15
|
+
console.log(` ${red("\u2718")} Node.js ${nodeVersion} -- requires >=18`);
|
|
16
|
+
allOk = false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Check API key
|
|
20
|
+
const { value: apiKey, source } = resolve("api_key", flags.apiKey);
|
|
21
|
+
if (apiKey && apiKey.trim()) {
|
|
22
|
+
const masked = apiKey.slice(0, 6) + "..." + apiKey.slice(-4);
|
|
23
|
+
console.log(` ${green("\u2713")} API key configured (${masked} via ${source})`);
|
|
24
|
+
|
|
25
|
+
// Test connectivity
|
|
26
|
+
process.stderr.write(` \u2026 Testing API connectivity...\r`);
|
|
27
|
+
try {
|
|
28
|
+
await discoverTools({ apiKey, baseUrl: flags.baseUrl, query: "test", limit: 1, timeoutMs: 10000 });
|
|
29
|
+
console.log(` ${green("\u2713")} API connectivity OK `);
|
|
30
|
+
} catch (err) {
|
|
31
|
+
console.log(` ${red("\u2718")} API connectivity failed: ${err.message} `);
|
|
32
|
+
allOk = false;
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
console.log(` ${red("\u2718")} No API key configured`);
|
|
36
|
+
console.log(` Run ${cyan("qveris login")} or set QVERIS_API_KEY`);
|
|
37
|
+
allOk = false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
console.log();
|
|
41
|
+
if (allOk) {
|
|
42
|
+
console.log(` ${green("All checks passed.")}\n`);
|
|
43
|
+
} else {
|
|
44
|
+
console.log(` ${red("Some checks failed.")}\n`);
|
|
45
|
+
process.exitCode = 1;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { readSession, writeSession } from "../session/session.mjs";
|
|
2
|
+
import { bold, dim, cyan } from "../output/colors.mjs";
|
|
3
|
+
import { outputJson } from "../output/json.mjs";
|
|
4
|
+
|
|
5
|
+
export async function runHistory(flags) {
|
|
6
|
+
const session = readSession();
|
|
7
|
+
|
|
8
|
+
if (!session) {
|
|
9
|
+
if (flags.json) {
|
|
10
|
+
outputJson({ session: null });
|
|
11
|
+
} else {
|
|
12
|
+
console.log(`\n ${dim("No active session. Run")} ${cyan("qveris discover")} ${dim("to start one.")}\n`);
|
|
13
|
+
}
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (flags.clear) {
|
|
18
|
+
writeSession({});
|
|
19
|
+
console.log(" Session cleared.");
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (flags.json) {
|
|
24
|
+
outputJson(session);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
console.log(`\n ${bold("Current Session")}\n`);
|
|
29
|
+
console.log(` Query: ${session.query || dim("N/A")}`);
|
|
30
|
+
console.log(` Discovery ID: ${dim(session.discoveryId || "N/A")}`);
|
|
31
|
+
|
|
32
|
+
const results = session.results ?? [];
|
|
33
|
+
if (results.length > 0) {
|
|
34
|
+
console.log(` Results:`);
|
|
35
|
+
for (const r of results) {
|
|
36
|
+
console.log(` ${dim(String(r.index))} ${cyan(r.tool_id)} ${r.name || ""}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (session.timestamp) {
|
|
41
|
+
const age = Math.round((Date.now() - session.timestamp) / 1000);
|
|
42
|
+
const mins = Math.floor(age / 60);
|
|
43
|
+
const secs = age % 60;
|
|
44
|
+
console.log(` Age: ${dim(mins > 0 ? `${mins}m ${secs}s` : `${secs}s`)}`);
|
|
45
|
+
}
|
|
46
|
+
console.log();
|
|
47
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { resolveApiKey } from "../client/auth.mjs";
|
|
2
|
+
import { inspectToolsByIds } from "../client/api.mjs";
|
|
3
|
+
import { resolveToolId, getSessionDiscoveryId } from "../session/session.mjs";
|
|
4
|
+
import { formatInspectResult } from "../output/formatter.mjs";
|
|
5
|
+
import { outputJson } from "../output/json.mjs";
|
|
6
|
+
import { createSpinner } from "../output/spinner.mjs";
|
|
7
|
+
|
|
8
|
+
export async function runInspect(idsOrIndexes, flags) {
|
|
9
|
+
const apiKey = resolveApiKey(flags.apiKey);
|
|
10
|
+
const timeoutMs = (parseInt(flags.timeout, 10) || 30) * 1000;
|
|
11
|
+
|
|
12
|
+
const toolIds = [];
|
|
13
|
+
let discoveryId = flags.discoveryId || null;
|
|
14
|
+
|
|
15
|
+
for (const raw of idsOrIndexes) {
|
|
16
|
+
const resolved = resolveToolId(raw);
|
|
17
|
+
toolIds.push(resolved.toolId);
|
|
18
|
+
if (resolved.fromSession && resolved.discoveryId && !discoveryId) {
|
|
19
|
+
discoveryId = resolved.discoveryId;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!discoveryId) discoveryId = getSessionDiscoveryId();
|
|
24
|
+
|
|
25
|
+
const spinner = flags.json ? { stop() {} } : createSpinner("Inspecting tools...");
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const result = await inspectToolsByIds({
|
|
29
|
+
apiKey,
|
|
30
|
+
baseUrl: flags.baseUrl,
|
|
31
|
+
toolIds,
|
|
32
|
+
discoveryId,
|
|
33
|
+
timeoutMs,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
spinner.stop();
|
|
37
|
+
|
|
38
|
+
if (flags.json) {
|
|
39
|
+
outputJson(result);
|
|
40
|
+
} else {
|
|
41
|
+
console.log(formatInspectResult(result));
|
|
42
|
+
}
|
|
43
|
+
} catch (err) {
|
|
44
|
+
spinner.stop();
|
|
45
|
+
throw err;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { createInterface } from "node:readline";
|
|
2
|
+
import { resolveApiKey } from "../client/auth.mjs";
|
|
3
|
+
import { discoverTools, inspectToolsByIds, callTool } from "../client/api.mjs";
|
|
4
|
+
import { resolveParams } from "../utils/params.mjs";
|
|
5
|
+
import { formatDiscoverResult, formatInspectResult, formatCallResult } from "../output/formatter.mjs";
|
|
6
|
+
import { generateSnippet } from "../output/codegen.mjs";
|
|
7
|
+
import { writeSession } from "../session/session.mjs";
|
|
8
|
+
import { bold, dim, cyan } from "../output/colors.mjs";
|
|
9
|
+
import { handleError } from "../errors/handler.mjs";
|
|
10
|
+
import { createSpinner } from "../output/spinner.mjs";
|
|
11
|
+
|
|
12
|
+
export async function runInteractive(flags) {
|
|
13
|
+
const apiKey = resolveApiKey(flags.apiKey);
|
|
14
|
+
const baseUrl = flags.baseUrl;
|
|
15
|
+
const limit = parseInt(flags.limit, 10) || 5;
|
|
16
|
+
const discoverTimeout = (parseInt(flags.timeout, 10) || 30) * 1000;
|
|
17
|
+
const callTimeout = (parseInt(flags.timeout, 10) || 60) * 1000;
|
|
18
|
+
|
|
19
|
+
const state = {
|
|
20
|
+
discoveryId: null,
|
|
21
|
+
results: [],
|
|
22
|
+
lastCallContext: null,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
console.log(`\n ${bold("QVeris Interactive Mode")}`);
|
|
26
|
+
console.log(` ${dim("Type 'help' for commands, 'exit' to quit.")}\n`);
|
|
27
|
+
|
|
28
|
+
const rl = createInterface({
|
|
29
|
+
input: process.stdin,
|
|
30
|
+
output: process.stdout,
|
|
31
|
+
prompt: `${cyan("qveris")}${dim(">")} `,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
rl.prompt();
|
|
35
|
+
|
|
36
|
+
rl.on("line", async (line) => {
|
|
37
|
+
const input = line.trim();
|
|
38
|
+
if (!input) { rl.prompt(); return; }
|
|
39
|
+
|
|
40
|
+
rl.pause(); // Prevent race conditions on rapid input
|
|
41
|
+
|
|
42
|
+
const parts = parseArgs(input);
|
|
43
|
+
const cmd = parts[0]?.toLowerCase();
|
|
44
|
+
const rest = parts.slice(1);
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
switch (cmd) {
|
|
48
|
+
case "discover":
|
|
49
|
+
case "search": {
|
|
50
|
+
const query = rest.join(" ");
|
|
51
|
+
if (!query) { console.log(" Usage: discover <query>"); break; }
|
|
52
|
+
const sp = createSpinner("Discovering...");
|
|
53
|
+
const result = await discoverTools({ apiKey, baseUrl, query, limit, timeoutMs: discoverTimeout });
|
|
54
|
+
sp.stop();
|
|
55
|
+
state.discoveryId = result.search_id;
|
|
56
|
+
state.results = (result.results ?? []).map((t, i) => ({
|
|
57
|
+
index: i + 1, tool_id: t.tool_id, name: t.name, provider_name: t.provider_name,
|
|
58
|
+
}));
|
|
59
|
+
// Persist session so index shortcuts work in subsequent non-interactive calls
|
|
60
|
+
writeSession({ discoveryId: result.search_id, query, results: state.results });
|
|
61
|
+
console.log(formatDiscoverResult(result));
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
case "inspect": {
|
|
65
|
+
const toolId = resolveId(rest[0], state);
|
|
66
|
+
if (!toolId) { console.log(" Usage: inspect <index|tool_id>"); break; }
|
|
67
|
+
const sp2 = createSpinner("Inspecting...");
|
|
68
|
+
const result = await inspectToolsByIds({ apiKey, baseUrl, toolIds: [toolId], discoveryId: state.discoveryId, timeoutMs: discoverTimeout });
|
|
69
|
+
sp2.stop();
|
|
70
|
+
console.log(formatInspectResult(result));
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
case "call": {
|
|
74
|
+
const toolId = resolveId(rest[0], state);
|
|
75
|
+
if (!toolId) { console.log(" Usage: call <index|tool_id> <json_params>"); break; }
|
|
76
|
+
if (!state.discoveryId) { console.log(" Run 'discover' first."); break; }
|
|
77
|
+
const paramsStr = rest.slice(1).join(" ") || "{}";
|
|
78
|
+
const parameters = resolveParams(paramsStr);
|
|
79
|
+
const sp3 = createSpinner("Calling...");
|
|
80
|
+
const result = await callTool({ apiKey, baseUrl, toolId, discoveryId: state.discoveryId, parameters, timeoutMs: callTimeout });
|
|
81
|
+
sp3.stop();
|
|
82
|
+
console.log(formatCallResult(result));
|
|
83
|
+
if (result.success) {
|
|
84
|
+
state.lastCallContext = { toolId, discoveryId: state.discoveryId, parameters };
|
|
85
|
+
}
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
case "codegen": {
|
|
89
|
+
if (!state.lastCallContext) { console.log(" No successful call yet."); break; }
|
|
90
|
+
const lang = rest[0] || "curl";
|
|
91
|
+
console.log(`\n${generateSnippet(lang, state.lastCallContext)}\n`);
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
case "history": {
|
|
95
|
+
if (!state.discoveryId) { console.log(" No session."); break; }
|
|
96
|
+
console.log(`\n Discovery ID: ${dim(state.discoveryId)}`);
|
|
97
|
+
for (const r of state.results) {
|
|
98
|
+
console.log(` ${dim(String(r.index))} ${cyan(r.tool_id)}`);
|
|
99
|
+
}
|
|
100
|
+
console.log();
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
case "help":
|
|
104
|
+
printHelp();
|
|
105
|
+
break;
|
|
106
|
+
case "exit":
|
|
107
|
+
case "quit":
|
|
108
|
+
rl.close();
|
|
109
|
+
return;
|
|
110
|
+
default:
|
|
111
|
+
console.log(` Unknown command: ${cmd}. Type 'help' for commands.`);
|
|
112
|
+
}
|
|
113
|
+
} catch (err) {
|
|
114
|
+
handleError(err, false);
|
|
115
|
+
} finally {
|
|
116
|
+
rl.resume();
|
|
117
|
+
rl.prompt();
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
rl.on("close", () => {
|
|
122
|
+
console.log(`\n ${dim("Bye.")}\n`);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function resolveId(raw, state) {
|
|
127
|
+
if (!raw) return null;
|
|
128
|
+
if (/^\d+$/.test(raw)) {
|
|
129
|
+
const index = parseInt(raw, 10) - 1;
|
|
130
|
+
if (index >= 0 && index < state.results.length) {
|
|
131
|
+
return state.results[index].tool_id;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return raw;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Shell-style argument splitting: handles double quotes, single quotes, and backslash escapes. */
|
|
138
|
+
function parseArgs(input) {
|
|
139
|
+
const args = [];
|
|
140
|
+
let current = "";
|
|
141
|
+
let inDouble = false;
|
|
142
|
+
let inSingle = false;
|
|
143
|
+
let escape = false;
|
|
144
|
+
|
|
145
|
+
for (const ch of input) {
|
|
146
|
+
if (escape) { current += ch; escape = false; continue; }
|
|
147
|
+
if (ch === "\\" && !inSingle) { escape = true; continue; }
|
|
148
|
+
if (ch === '"' && !inSingle) { inDouble = !inDouble; continue; }
|
|
149
|
+
if (ch === "'" && !inDouble) { inSingle = !inSingle; continue; }
|
|
150
|
+
if ((ch === " " || ch === "\t") && !inDouble && !inSingle) {
|
|
151
|
+
if (current) { args.push(current); current = ""; }
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
current += ch;
|
|
155
|
+
}
|
|
156
|
+
if (current) args.push(current);
|
|
157
|
+
return args;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function printHelp() {
|
|
161
|
+
console.log(`
|
|
162
|
+
${bold("Commands:")}
|
|
163
|
+
discover <query> Find capabilities
|
|
164
|
+
inspect <index|tool_id> View tool details
|
|
165
|
+
call <index|tool_id> {} Execute a tool with JSON params
|
|
166
|
+
codegen <curl|js|python> Generate code from last call
|
|
167
|
+
history Show session state
|
|
168
|
+
help Show this help
|
|
169
|
+
exit Quit
|
|
170
|
+
`);
|
|
171
|
+
}
|