@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,75 @@
|
|
|
1
|
+
import { resolve } from "../config/resolve.mjs";
|
|
2
|
+
|
|
3
|
+
export function generateSnippet(lang, { toolId, discoveryId, parameters, maxResponseSize = 20480 }) {
|
|
4
|
+
const baseUrl = resolve("base_url").value;
|
|
5
|
+
|
|
6
|
+
switch (lang) {
|
|
7
|
+
case "curl":
|
|
8
|
+
return generateCurl({ baseUrl, toolId, discoveryId, parameters, maxResponseSize });
|
|
9
|
+
case "js":
|
|
10
|
+
case "javascript":
|
|
11
|
+
return generateJs({ baseUrl, toolId, discoveryId, parameters, maxResponseSize });
|
|
12
|
+
case "python":
|
|
13
|
+
case "py":
|
|
14
|
+
return generatePython({ baseUrl, toolId, discoveryId, parameters, maxResponseSize });
|
|
15
|
+
default:
|
|
16
|
+
return `Unsupported language: ${lang}. Use: curl, js, python`;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function generateCurl({ baseUrl, toolId, discoveryId, parameters, maxResponseSize }) {
|
|
21
|
+
const body = JSON.stringify({
|
|
22
|
+
search_id: discoveryId,
|
|
23
|
+
parameters,
|
|
24
|
+
max_response_size: maxResponseSize,
|
|
25
|
+
}, null, 2);
|
|
26
|
+
|
|
27
|
+
// Use heredoc to avoid single-quote escaping issues in parameters
|
|
28
|
+
return `curl -sS -X POST "${baseUrl}/tools/execute?tool_id=${toolId}" \\
|
|
29
|
+
-H "Authorization: Bearer $QVERIS_API_KEY" \\
|
|
30
|
+
-H "Content-Type: application/json" \\
|
|
31
|
+
-d @- <<'EOF'
|
|
32
|
+
${body}
|
|
33
|
+
EOF`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function generateJs({ baseUrl, toolId, discoveryId, parameters, maxResponseSize }) {
|
|
37
|
+
const paramsStr = JSON.stringify(parameters, null, 4);
|
|
38
|
+
return `const resp = await fetch(
|
|
39
|
+
"${baseUrl}/tools/execute?tool_id=${toolId}",
|
|
40
|
+
{
|
|
41
|
+
method: "POST",
|
|
42
|
+
headers: {
|
|
43
|
+
Authorization: \`Bearer \${process.env.QVERIS_API_KEY}\`,
|
|
44
|
+
"Content-Type": "application/json",
|
|
45
|
+
},
|
|
46
|
+
body: JSON.stringify({
|
|
47
|
+
search_id: "${discoveryId}",
|
|
48
|
+
parameters: ${paramsStr},
|
|
49
|
+
max_response_size: ${maxResponseSize},
|
|
50
|
+
}),
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
const data = await resp.json();
|
|
54
|
+
console.log(data);`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function generatePython({ baseUrl, toolId, discoveryId, parameters, maxResponseSize }) {
|
|
58
|
+
const paramsStr = JSON.stringify(parameters, null, 8);
|
|
59
|
+
return `import os
|
|
60
|
+
import requests
|
|
61
|
+
|
|
62
|
+
resp = requests.post(
|
|
63
|
+
"${baseUrl}/tools/execute",
|
|
64
|
+
params={"tool_id": "${toolId}"},
|
|
65
|
+
headers={"Authorization": f"Bearer {os.environ['QVERIS_API_KEY']}"},
|
|
66
|
+
json={
|
|
67
|
+
"search_id": "${discoveryId}",
|
|
68
|
+
"parameters": ${paramsStr},
|
|
69
|
+
"max_response_size": ${maxResponseSize},
|
|
70
|
+
},
|
|
71
|
+
timeout=60,
|
|
72
|
+
)
|
|
73
|
+
data = resp.json()
|
|
74
|
+
print(data)`;
|
|
75
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const enabled =
|
|
2
|
+
!process.env.NO_COLOR &&
|
|
3
|
+
(process.env.FORCE_COLOR === "1" || (process.stdout.isTTY && process.stderr.isTTY));
|
|
4
|
+
|
|
5
|
+
const code = (open, close) =>
|
|
6
|
+
enabled ? (s) => `\x1b[${open}m${s}\x1b[${close}m` : (s) => s;
|
|
7
|
+
|
|
8
|
+
export const bold = code("1", "22");
|
|
9
|
+
export const dim = code("2", "22");
|
|
10
|
+
export const red = code("31", "39");
|
|
11
|
+
export const green = code("32", "39");
|
|
12
|
+
export const yellow = code("33", "39");
|
|
13
|
+
export const cyan = code("36", "39");
|
|
14
|
+
export const gray = code("90", "39");
|
|
15
|
+
|
|
16
|
+
export const isColorEnabled = () => enabled;
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { bold, cyan, dim, green, yellow, red } from "./colors.mjs";
|
|
2
|
+
|
|
3
|
+
// ── discover ──────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
export function formatDiscoverResult(result) {
|
|
6
|
+
const tools = result.results ?? [];
|
|
7
|
+
const total = result.total ?? tools.length;
|
|
8
|
+
const searchTime = result.elapsed_time_ms;
|
|
9
|
+
const remaining = result.remaining_credits;
|
|
10
|
+
const lines = [];
|
|
11
|
+
|
|
12
|
+
if (tools.length === 0) {
|
|
13
|
+
lines.push("No tools matched your query. Try a broader description.");
|
|
14
|
+
return lines.join("\n");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
lines.push(`Found ${bold(String(total))} capabilities matching your query`);
|
|
18
|
+
lines.push("");
|
|
19
|
+
|
|
20
|
+
for (let i = 0; i < tools.length; i++) {
|
|
21
|
+
const t = tools[i];
|
|
22
|
+
const name = t.name ?? "N/A";
|
|
23
|
+
const toolId = t.tool_id ?? "";
|
|
24
|
+
const desc = stringifyDesc(t.description);
|
|
25
|
+
const provider = t.provider_name ?? "";
|
|
26
|
+
const categories = Array.isArray(t.categories) ? t.categories.join(", ") : "";
|
|
27
|
+
const region = t.region ?? "global";
|
|
28
|
+
const stats = t.stats ?? {};
|
|
29
|
+
|
|
30
|
+
// Relevance score
|
|
31
|
+
const score = t.final_score;
|
|
32
|
+
const scoreStr = typeof score === "number" ? `${(score * 100).toFixed(0)}%` : "";
|
|
33
|
+
|
|
34
|
+
// Quality metrics
|
|
35
|
+
let successRate = stats.success_rate;
|
|
36
|
+
successRate = typeof successRate === "number" ? `${(successRate * 100).toFixed(1)}%` : "N/A";
|
|
37
|
+
let avgTime = stats.avg_execution_time_ms;
|
|
38
|
+
avgTime = typeof avgTime === "number" ? `~${Math.round(avgTime)}ms` : "N/A";
|
|
39
|
+
const cost = stats.cost ?? "?";
|
|
40
|
+
|
|
41
|
+
// Verified indicator
|
|
42
|
+
const verified = t.has_last_execution ? green(" \u2713") : "";
|
|
43
|
+
|
|
44
|
+
// Line 1: index + name + provider
|
|
45
|
+
const providerPart = provider ? ` ${dim("by")} ${provider}` : "";
|
|
46
|
+
lines.push(`${bold(String(i + 1) + ".")} ${cyan(name)}${providerPart}${verified}`);
|
|
47
|
+
|
|
48
|
+
// Line 2: tool_id
|
|
49
|
+
lines.push(` ${dim(toolId)}`);
|
|
50
|
+
|
|
51
|
+
// Line 3: description (truncated)
|
|
52
|
+
if (desc) {
|
|
53
|
+
lines.push(` ${desc.length > 100 ? desc.slice(0, 100) + "..." : desc}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Line 4: metrics row
|
|
57
|
+
const metricParts = [];
|
|
58
|
+
if (scoreStr) metricParts.push(`relevance: ${bold(scoreStr)}`);
|
|
59
|
+
metricParts.push(`success: ${green(successRate)}`);
|
|
60
|
+
metricParts.push(`latency: ${avgTime}`);
|
|
61
|
+
metricParts.push(`cost: ${yellow(String(cost))} cr`);
|
|
62
|
+
if (region !== "global") metricParts.push(`region: ${region}`);
|
|
63
|
+
lines.push(` ${dim(metricParts.join(" \u00b7 "))}`);
|
|
64
|
+
|
|
65
|
+
// Line 5: categories (if present)
|
|
66
|
+
if (categories) {
|
|
67
|
+
lines.push(` ${dim("tags:")} ${dim(categories)}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
lines.push("");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Footer
|
|
74
|
+
const footerParts = [`Discovery ID: ${result.search_id ?? "N/A"}`];
|
|
75
|
+
if (typeof searchTime === "number") footerParts.push(`${Math.round(searchTime)}ms`);
|
|
76
|
+
if (typeof remaining === "number") footerParts.push(`${remaining} credits remaining`);
|
|
77
|
+
lines.push(dim(footerParts.join(" \u00b7 ")));
|
|
78
|
+
|
|
79
|
+
return lines.join("\n");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ── inspect ───────────────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
export function formatInspectResult(tools) {
|
|
85
|
+
const list = Array.isArray(tools) ? tools : tools?.results ?? tools?.tools ?? [tools];
|
|
86
|
+
const remaining = tools?.remaining_credits;
|
|
87
|
+
const lines = [];
|
|
88
|
+
|
|
89
|
+
for (const t of list) {
|
|
90
|
+
const name = t.name ?? t.tool_id ?? "N/A";
|
|
91
|
+
const toolId = t.tool_id ?? "";
|
|
92
|
+
const desc = stringifyDesc(t.description);
|
|
93
|
+
const provider = t.provider_name ?? "";
|
|
94
|
+
const providerDesc = stringifyDesc(t.provider_description);
|
|
95
|
+
const categories = Array.isArray(t.categories) ? t.categories.join(", ") : "";
|
|
96
|
+
const region = t.region ?? "global";
|
|
97
|
+
const docsUrl = t.docs_url ?? "";
|
|
98
|
+
const stats = t.stats ?? {};
|
|
99
|
+
|
|
100
|
+
let successRate = stats.success_rate;
|
|
101
|
+
successRate = typeof successRate === "number" ? `${(successRate * 100).toFixed(1)}%` : "N/A";
|
|
102
|
+
let avgTime = stats.avg_execution_time_ms;
|
|
103
|
+
avgTime = typeof avgTime === "number" ? `~${Math.round(avgTime)}ms` : "N/A";
|
|
104
|
+
const cost = stats.cost ?? "?";
|
|
105
|
+
|
|
106
|
+
// Header
|
|
107
|
+
lines.push(bold(cyan(name)));
|
|
108
|
+
if (toolId && toolId !== name) lines.push(dim(toolId));
|
|
109
|
+
if (desc) lines.push(desc);
|
|
110
|
+
lines.push("");
|
|
111
|
+
|
|
112
|
+
// Metadata table
|
|
113
|
+
if (provider) {
|
|
114
|
+
const pLine = providerDesc ? `${provider} ${dim("— " + providerDesc)}` : provider;
|
|
115
|
+
lines.push(` Provider: ${pLine}`);
|
|
116
|
+
}
|
|
117
|
+
if (categories) lines.push(` Categories: ${categories}`);
|
|
118
|
+
lines.push(` Region: ${region}`);
|
|
119
|
+
lines.push(` Latency: ${bold(avgTime)}`);
|
|
120
|
+
lines.push(` Success: ${green(successRate)}`);
|
|
121
|
+
lines.push(` Cost: ${yellow(String(cost))} credits`);
|
|
122
|
+
if (t.has_last_execution) lines.push(` Verified: ${green("\u2713 has execution history")}`);
|
|
123
|
+
if (docsUrl) lines.push(` Docs: ${cyan(docsUrl)}`);
|
|
124
|
+
|
|
125
|
+
// Parameters
|
|
126
|
+
const params = t.params ?? [];
|
|
127
|
+
if (params.length > 0) {
|
|
128
|
+
lines.push("");
|
|
129
|
+
lines.push(bold(" Parameters:"));
|
|
130
|
+
for (const p of params) {
|
|
131
|
+
const req = p.required ? bold("required") : dim("optional");
|
|
132
|
+
const pType = dim(p.type ?? "string");
|
|
133
|
+
const pDesc = p.description ? stringifyDesc(p.description) : "";
|
|
134
|
+
lines.push(` ${cyan(p.name)} ${pType} ${req}`);
|
|
135
|
+
if (pDesc) lines.push(` ${dim(pDesc)}`);
|
|
136
|
+
if (Array.isArray(p.enum) && p.enum.length > 0) {
|
|
137
|
+
lines.push(` ${dim("values:")} ${p.enum.map((v) => yellow(JSON.stringify(v))).join(", ")}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Example
|
|
143
|
+
const examples = t.examples ?? {};
|
|
144
|
+
if (examples.sample_parameters) {
|
|
145
|
+
lines.push("");
|
|
146
|
+
lines.push(bold(" Example:"));
|
|
147
|
+
lines.push(` ${JSON.stringify(examples.sample_parameters)}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Last execution record
|
|
151
|
+
if (t.last_execution_record && typeof t.last_execution_record === "object") {
|
|
152
|
+
const rec = t.last_execution_record;
|
|
153
|
+
lines.push("");
|
|
154
|
+
lines.push(dim(" Last execution:"));
|
|
155
|
+
if (rec.success !== undefined) lines.push(` Status: ${rec.success ? green("success") : red("failed")}`);
|
|
156
|
+
if (rec.execution_time) lines.push(` Time: ${rec.execution_time}`);
|
|
157
|
+
if (rec.error_message) lines.push(` Error: ${red(rec.error_message)}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Footer
|
|
162
|
+
if (typeof remaining === "number") {
|
|
163
|
+
lines.push("");
|
|
164
|
+
lines.push(dim(`${remaining} credits remaining`));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return lines.join("\n");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ── call ──────────────────────────────────────────────────────────────
|
|
171
|
+
|
|
172
|
+
export function formatCallResult(result) {
|
|
173
|
+
const success = result.success ?? false;
|
|
174
|
+
const cost = result.cost ?? result.credits_used ?? 0;
|
|
175
|
+
const remaining = result.remaining_credits;
|
|
176
|
+
const executionId = result.execution_id;
|
|
177
|
+
const toolId = result.tool_id;
|
|
178
|
+
const lines = [];
|
|
179
|
+
|
|
180
|
+
// Status line
|
|
181
|
+
if (success) {
|
|
182
|
+
let timePart = "";
|
|
183
|
+
// elapsed_time_ms is in milliseconds; execution_time is in seconds
|
|
184
|
+
if (typeof result.elapsed_time_ms === "number") {
|
|
185
|
+
timePart = `${Math.round(result.elapsed_time_ms)}ms`;
|
|
186
|
+
} else if (typeof result.execution_time === "number") {
|
|
187
|
+
timePart = `${Math.round(result.execution_time * 1000)}ms`;
|
|
188
|
+
}
|
|
189
|
+
const parts = [`${green("\u2713")} ${bold("success")}`];
|
|
190
|
+
if (timePart) parts.push(dim(timePart));
|
|
191
|
+
if (cost) parts.push(`${yellow(String(cost))} credits`);
|
|
192
|
+
if (typeof remaining === "number") parts.push(dim(`(${remaining} remaining)`));
|
|
193
|
+
lines.push(parts.join(" \u00b7 "));
|
|
194
|
+
} else {
|
|
195
|
+
lines.push(`${red("\u2718")} ${bold("failed")}`);
|
|
196
|
+
const errMsg = result.error_message ?? "Unknown error";
|
|
197
|
+
lines.push(red(errMsg));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Execution metadata
|
|
201
|
+
const metaParts = [];
|
|
202
|
+
if (toolId) metaParts.push(`tool: ${toolId}`);
|
|
203
|
+
if (executionId) metaParts.push(`id: ${executionId}`);
|
|
204
|
+
if (metaParts.length > 0) lines.push(dim(metaParts.join(" \u00b7 ")));
|
|
205
|
+
|
|
206
|
+
// Result data
|
|
207
|
+
const data = result.result ?? {};
|
|
208
|
+
const fullContentUrl = typeof data.full_content_file_url === "string" ? data.full_content_file_url : null;
|
|
209
|
+
|
|
210
|
+
if (fullContentUrl) {
|
|
211
|
+
// ── Truncated result ──
|
|
212
|
+
const msg = data.message;
|
|
213
|
+
if (msg) lines.push(`\n${yellow(msg)}`);
|
|
214
|
+
|
|
215
|
+
lines.push(`\n${bold("Full content (valid 120 min):")}`);
|
|
216
|
+
lines.push(` ${cyan(fullContentUrl)}`);
|
|
217
|
+
lines.push(` ${dim("Download:")} curl -o result.json '${fullContentUrl}'`);
|
|
218
|
+
|
|
219
|
+
// Schema: compact one-line summary of the data structure
|
|
220
|
+
if (data.content_schema) {
|
|
221
|
+
lines.push(`\n${bold("Schema:")}`);
|
|
222
|
+
lines.push(formatSchema(data.content_schema, " "));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Truncated content preview
|
|
226
|
+
if (data.truncated_content) {
|
|
227
|
+
lines.push(`\n${bold("Preview:")}`);
|
|
228
|
+
const raw = typeof data.truncated_content === "string"
|
|
229
|
+
? data.truncated_content
|
|
230
|
+
: JSON.stringify(data.truncated_content, null, 2);
|
|
231
|
+
const previewLines = raw.slice(0, 800).split("\n").slice(0, 15);
|
|
232
|
+
for (const l of previewLines) lines.push(dim(` ${l}`));
|
|
233
|
+
if (raw.length > 800) lines.push(dim(" ..."));
|
|
234
|
+
lines.push(`\n${dim("Use --max-size -1 for full output, or download the file above.")}`);
|
|
235
|
+
}
|
|
236
|
+
} else if (data.data !== undefined) {
|
|
237
|
+
// Standard result with data field
|
|
238
|
+
lines.push(`\n${JSON.stringify(data.data, null, 2)}`);
|
|
239
|
+
} else if (Object.keys(data).length > 0) {
|
|
240
|
+
// Fallback: raw result object
|
|
241
|
+
lines.push(`\n${JSON.stringify(data, null, 2)}`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return lines.join("\n");
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ── helpers ───────────────────────────────────────────────────────────
|
|
248
|
+
|
|
249
|
+
/** Format a JSON Schema into a compact readable tree. */
|
|
250
|
+
function formatSchema(schema, indent = "", depth = 0) {
|
|
251
|
+
if (depth > 8 || !schema || typeof schema !== "object") return `${indent}${dim(schema?.type ?? "(unknown)")}`;
|
|
252
|
+
const lines = [];
|
|
253
|
+
if (schema.type === "object" && schema.properties) {
|
|
254
|
+
for (const [key, val] of Object.entries(schema.properties)) {
|
|
255
|
+
const type = val.type ?? "any";
|
|
256
|
+
if (type === "array" && val.items?.properties) {
|
|
257
|
+
lines.push(`${indent}${cyan(key)}: ${dim(type + " of")}`);
|
|
258
|
+
lines.push(formatSchema(val.items, indent + " ", depth + 1));
|
|
259
|
+
} else if (type === "object" && val.properties) {
|
|
260
|
+
lines.push(`${indent}${cyan(key)}: ${dim(type)}`);
|
|
261
|
+
lines.push(formatSchema(val, indent + " ", depth + 1));
|
|
262
|
+
} else {
|
|
263
|
+
lines.push(`${indent}${cyan(key)}: ${dim(type)}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
} else {
|
|
267
|
+
lines.push(`${indent}${dim(schema.type ?? "any")}`);
|
|
268
|
+
}
|
|
269
|
+
return lines.join("\n");
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** Handle description that may be a string, i18n object {en: "...", zh: "..."}, or other. */
|
|
273
|
+
function stringifyDesc(desc) {
|
|
274
|
+
if (!desc) return "";
|
|
275
|
+
if (typeof desc === "string") return desc;
|
|
276
|
+
if (typeof desc === "object") {
|
|
277
|
+
return desc.en || desc.zh || desc["zh-CN"] || Object.values(desc).find((v) => typeof v === "string") || "";
|
|
278
|
+
}
|
|
279
|
+
return String(desc);
|
|
280
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function outputJson(data) {
|
|
2
|
+
process.stdout.write(JSON.stringify(data, null, 2) + "\n");
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function outputJsonError(error, exitCode = 1) {
|
|
6
|
+
const obj = { error: error.message || String(error) };
|
|
7
|
+
if (error.code) obj.code = error.code;
|
|
8
|
+
if (error.hint) obj.hint = error.hint;
|
|
9
|
+
obj.exit_code = exitCode;
|
|
10
|
+
process.stderr.write(JSON.stringify(obj) + "\n");
|
|
11
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
2
|
+
|
|
3
|
+
export function createSpinner(message) {
|
|
4
|
+
if (!process.stderr.isTTY) return { stop() {} };
|
|
5
|
+
|
|
6
|
+
let i = 0;
|
|
7
|
+
const timer = setInterval(() => {
|
|
8
|
+
process.stderr.write(`\r ${FRAMES[i++ % FRAMES.length]} ${message}`);
|
|
9
|
+
}, 80);
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
stop(clearLine = true) {
|
|
13
|
+
clearInterval(timer);
|
|
14
|
+
if (clearLine) process.stderr.write("\r\x1b[K");
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { join, dirname } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
|
|
5
|
+
const SESSION_TTL_MS = 30 * 60 * 1000;
|
|
6
|
+
|
|
7
|
+
function sessionPath() {
|
|
8
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
9
|
+
const base = xdg || join(homedir(), ".config");
|
|
10
|
+
return join(base, "qveris", ".session.json");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function readSession() {
|
|
14
|
+
try {
|
|
15
|
+
const data = JSON.parse(readFileSync(sessionPath(), "utf-8"));
|
|
16
|
+
if (data.timestamp && Date.now() - data.timestamp > SESSION_TTL_MS) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
return data;
|
|
20
|
+
} catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function writeSession(data) {
|
|
26
|
+
const dir = dirname(sessionPath());
|
|
27
|
+
mkdirSync(dir, { recursive: true });
|
|
28
|
+
writeFileSync(sessionPath(), JSON.stringify({ ...data, timestamp: Date.now() }, null, 2) + "\n", { mode: 0o600 });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function resolveToolId(idOrIndex) {
|
|
32
|
+
if (!/^\d+$/.test(idOrIndex)) return { toolId: idOrIndex, fromSession: false };
|
|
33
|
+
|
|
34
|
+
const session = readSession();
|
|
35
|
+
if (!session || !session.results) return { toolId: idOrIndex, fromSession: false };
|
|
36
|
+
|
|
37
|
+
const index = parseInt(idOrIndex, 10) - 1;
|
|
38
|
+
if (index < 0 || index >= session.results.length) {
|
|
39
|
+
return { toolId: idOrIndex, fromSession: false };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
toolId: session.results[index].tool_id,
|
|
44
|
+
name: session.results[index].name,
|
|
45
|
+
discoveryId: session.discoveryId,
|
|
46
|
+
fromSession: true,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function getSessionDiscoveryId() {
|
|
51
|
+
const session = readSession();
|
|
52
|
+
return session?.discoveryId || null;
|
|
53
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { CliError } from "../errors/handler.mjs";
|
|
3
|
+
|
|
4
|
+
export function resolveParams(value) {
|
|
5
|
+
if (!value || value === "{}") return {};
|
|
6
|
+
|
|
7
|
+
let raw = value;
|
|
8
|
+
|
|
9
|
+
if (value === "-") {
|
|
10
|
+
if (process.stdin.isTTY) {
|
|
11
|
+
throw new CliError("PARAMS_INVALID_JSON", "No data piped to stdin. Usage: echo '{...}' | qveris call ... --params -");
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
// Use fd 0 (stdin) which works cross-platform (Linux, macOS, Windows)
|
|
15
|
+
// Limit to 1MB to prevent excessive memory usage from large piped input
|
|
16
|
+
const MAX_STDIN = 1024 * 1024;
|
|
17
|
+
raw = readFileSync(0, "utf-8").slice(0, MAX_STDIN);
|
|
18
|
+
} catch {
|
|
19
|
+
throw new CliError("PARAMS_INVALID_JSON", "Failed to read from stdin");
|
|
20
|
+
}
|
|
21
|
+
} else if (value.startsWith("@")) {
|
|
22
|
+
const filePath = value.slice(1);
|
|
23
|
+
try {
|
|
24
|
+
raw = readFileSync(filePath, "utf-8");
|
|
25
|
+
} catch (err) {
|
|
26
|
+
throw new CliError("PARAMS_INVALID_JSON", `Cannot read params file: ${filePath}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(raw);
|
|
32
|
+
} catch (err) {
|
|
33
|
+
throw new CliError("PARAMS_INVALID_JSON", `${err.message}`);
|
|
34
|
+
}
|
|
35
|
+
}
|