@qverisai/cli 0.3.0 → 0.4.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 +11 -6
- package/package.json +1 -1
- package/src/commands/config.mjs +9 -1
- package/src/commands/credits.mjs +5 -2
- package/src/commands/discover.mjs +4 -0
- package/src/commands/doctor.mjs +5 -0
- package/src/commands/interactive.mjs +3 -1
- package/src/commands/login.mjs +77 -9
- package/src/config/region.mjs +20 -0
- package/src/main.mjs +7 -1
package/README.md
CHANGED
|
@@ -104,9 +104,9 @@ qveris call 1 --params '{"wfo": "LWX", "x": 90, "y": 90}'
|
|
|
104
104
|
|
|
105
105
|
| Command | Description |
|
|
106
106
|
|---------|-------------|
|
|
107
|
-
| `qveris login` | Authenticate with API key (opens browser or `--token` for direct input) |
|
|
107
|
+
| `qveris login` | Authenticate with API key (interactive region selection, opens browser, or `--token` for direct input) |
|
|
108
108
|
| `qveris logout` | Remove stored key |
|
|
109
|
-
| `qveris whoami` | Show current auth status
|
|
109
|
+
| `qveris whoami` | Show current auth status, key source, and region |
|
|
110
110
|
| `qveris credits` | Check credit balance |
|
|
111
111
|
|
|
112
112
|
### Utilities
|
|
@@ -114,7 +114,7 @@ qveris call 1 --params '{"wfo": "LWX", "x": 90, "y": 90}'
|
|
|
114
114
|
| Command | Description |
|
|
115
115
|
|---------|-------------|
|
|
116
116
|
| `qveris interactive` | Launch REPL mode (discover/inspect/call/codegen in one session) |
|
|
117
|
-
| `qveris doctor` | Self-check: Node.js version, API key, connectivity |
|
|
117
|
+
| `qveris doctor` | Self-check: Node.js version, API key, region, connectivity |
|
|
118
118
|
| `qveris config <subcommand>` | Manage CLI settings (set, get, list, reset, path) |
|
|
119
119
|
| `qveris completions <shell>` | Generate shell completions (bash/zsh/fish) |
|
|
120
120
|
|
|
@@ -251,13 +251,18 @@ The API region is auto-detected from your key prefix:
|
|
|
251
251
|
| `sk-xxx` | Global | `https://qveris.ai/api/v1` |
|
|
252
252
|
| `sk-cn-xxx` | China | `https://qveris.cn/api/v1` |
|
|
253
253
|
|
|
254
|
-
No extra configuration needed.
|
|
254
|
+
No extra configuration needed. `qveris login` prompts for region selection interactively on first use.
|
|
255
|
+
|
|
256
|
+
**Agent / script usage:** Use `--token` with a region-prefixed key, or set environment variables:
|
|
255
257
|
|
|
256
258
|
```bash
|
|
257
|
-
#
|
|
259
|
+
# Key prefix auto-detection (recommended)
|
|
260
|
+
qveris login --token "sk-cn-xxx"
|
|
261
|
+
|
|
262
|
+
# Or environment variable
|
|
258
263
|
export QVERIS_REGION=cn
|
|
259
264
|
|
|
260
|
-
# Or
|
|
265
|
+
# Or custom base URL
|
|
261
266
|
export QVERIS_BASE_URL=https://custom.endpoint/api/v1
|
|
262
267
|
|
|
263
268
|
# Or per-command
|
package/package.json
CHANGED
package/src/commands/config.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getConfigPath, getConfigValue, setConfigValue, writeConfig } from "../config/store.mjs";
|
|
2
|
-
import { resolveAll } from "../config/resolve.mjs";
|
|
2
|
+
import { resolve, resolveAll } from "../config/resolve.mjs";
|
|
3
|
+
import { resolveBaseUrl } from "../config/region.mjs";
|
|
3
4
|
import { bold, dim, cyan } from "../output/colors.mjs";
|
|
4
5
|
import { outputJson } from "../output/json.mjs";
|
|
5
6
|
|
|
@@ -64,11 +65,17 @@ function configGet(key, flags) {
|
|
|
64
65
|
|
|
65
66
|
function configList(flags) {
|
|
66
67
|
const all = resolveAll();
|
|
68
|
+
|
|
69
|
+
// Resolve effective region
|
|
70
|
+
const { value: apiKey } = resolve("api_key", flags.apiKey);
|
|
71
|
+
const { region, source: regionSource, baseUrl } = resolveBaseUrl({ baseUrlFlag: flags.baseUrl, apiKey });
|
|
72
|
+
|
|
67
73
|
if (flags.json) {
|
|
68
74
|
const obj = {};
|
|
69
75
|
for (const [k, v] of Object.entries(all)) {
|
|
70
76
|
obj[k] = { value: k === "api_key" && v.value ? mask(v.value) : v.value, source: v.source };
|
|
71
77
|
}
|
|
78
|
+
obj._region = { region, source: regionSource, baseUrl };
|
|
72
79
|
outputJson(obj);
|
|
73
80
|
return;
|
|
74
81
|
}
|
|
@@ -78,6 +85,7 @@ function configList(flags) {
|
|
|
78
85
|
const display = key === "api_key" && value ? mask(value) : String(value ?? dim("(not set)"));
|
|
79
86
|
console.log(` ${cyan(key.padEnd(20))}${display.padEnd(25)}${dim(source)}`);
|
|
80
87
|
}
|
|
88
|
+
console.log(`\n ${bold("Effective region:")} ${region} ${dim(`(${regionSource})`)} → ${dim(baseUrl)}`);
|
|
81
89
|
console.log();
|
|
82
90
|
}
|
|
83
91
|
|
package/src/commands/credits.mjs
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { resolveApiKey } from "../client/auth.mjs";
|
|
2
2
|
import { discoverTools } from "../client/api.mjs";
|
|
3
|
+
import { resolveBaseUrl, getSiteUrl } from "../config/region.mjs";
|
|
3
4
|
import { bold, dim, cyan, yellow, green } from "../output/colors.mjs";
|
|
4
5
|
import { outputJson } from "../output/json.mjs";
|
|
5
6
|
import { createSpinner } from "../output/spinner.mjs";
|
|
6
7
|
|
|
7
8
|
export async function runCredits(flags) {
|
|
8
9
|
const apiKey = resolveApiKey(flags.apiKey);
|
|
10
|
+
const { region, baseUrl } = resolveBaseUrl({ baseUrlFlag: flags.baseUrl, apiKey });
|
|
11
|
+
const accountUrl = `${getSiteUrl(region, baseUrl)}/account`;
|
|
9
12
|
|
|
10
13
|
const spinner = flags.json ? { stop() {} } : createSpinner("Checking credits...");
|
|
11
14
|
|
|
@@ -20,10 +23,10 @@ export async function runCredits(flags) {
|
|
|
20
23
|
outputJson({ remaining_credits: credits ?? null });
|
|
21
24
|
} else if (credits !== undefined && credits !== null) {
|
|
22
25
|
console.log(`\n ${green("\u2713")} Credits remaining: ${bold(yellow(String(credits)))}`);
|
|
23
|
-
console.log(` ${dim("Manage at:")} ${cyan(
|
|
26
|
+
console.log(` ${dim("Manage at:")} ${cyan(accountUrl)}\n`);
|
|
24
27
|
} else {
|
|
25
28
|
console.log(`\n ${dim("Credit balance not available in API response.")}`);
|
|
26
|
-
console.log(` Check at: ${cyan(
|
|
29
|
+
console.log(` Check at: ${cyan(accountUrl)}\n`);
|
|
27
30
|
}
|
|
28
31
|
} catch (err) {
|
|
29
32
|
spinner.stop();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { resolveApiKey } from "../client/auth.mjs";
|
|
2
2
|
import { discoverTools } from "../client/api.mjs";
|
|
3
|
+
import { resolveBaseUrl } from "../config/region.mjs";
|
|
3
4
|
import { writeSession } from "../session/session.mjs";
|
|
4
5
|
import { formatDiscoverResult } from "../output/formatter.mjs";
|
|
5
6
|
import { outputJson } from "../output/json.mjs";
|
|
@@ -25,9 +26,12 @@ export async function runDiscover(query, flags) {
|
|
|
25
26
|
|
|
26
27
|
// Store richer session data for index resolution
|
|
27
28
|
const tools = result.results ?? [];
|
|
29
|
+
const { region, baseUrl: resolvedBaseUrl } = resolveBaseUrl({ baseUrlFlag: flags.baseUrl, apiKey });
|
|
28
30
|
writeSession({
|
|
29
31
|
discoveryId: result.search_id,
|
|
30
32
|
query,
|
|
33
|
+
region,
|
|
34
|
+
baseUrl: resolvedBaseUrl,
|
|
31
35
|
results: tools.map((t, i) => ({
|
|
32
36
|
index: i + 1,
|
|
33
37
|
tool_id: t.tool_id,
|
package/src/commands/doctor.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { resolve } from "../config/resolve.mjs";
|
|
2
2
|
import { discoverTools } from "../client/api.mjs";
|
|
3
|
+
import { resolveBaseUrl } from "../config/region.mjs";
|
|
3
4
|
import { bold, green, red, dim, cyan } from "../output/colors.mjs";
|
|
4
5
|
|
|
5
6
|
export async function runDoctor(flags) {
|
|
@@ -22,6 +23,10 @@ export async function runDoctor(flags) {
|
|
|
22
23
|
const masked = apiKey.slice(0, 6) + "..." + apiKey.slice(-4);
|
|
23
24
|
console.log(` ${green("\u2713")} API key configured (${masked} via ${source})`);
|
|
24
25
|
|
|
26
|
+
// Show resolved region
|
|
27
|
+
const { baseUrl, region, source: regionSource } = resolveBaseUrl({ baseUrlFlag: flags.baseUrl, apiKey });
|
|
28
|
+
console.log(` ${green("\u2713")} Region: ${region} ${dim(`(${regionSource})`)} → ${dim(baseUrl)}`);
|
|
29
|
+
|
|
25
30
|
// Test connectivity
|
|
26
31
|
process.stderr.write(` \u2026 Testing API connectivity...\r`);
|
|
27
32
|
try {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createInterface } from "node:readline";
|
|
2
2
|
import { resolveApiKey } from "../client/auth.mjs";
|
|
3
3
|
import { discoverTools, inspectToolsByIds, callTool } from "../client/api.mjs";
|
|
4
|
+
import { resolveBaseUrl } from "../config/region.mjs";
|
|
4
5
|
import { resolveParams } from "../utils/params.mjs";
|
|
5
6
|
import { formatDiscoverResult, formatInspectResult, formatCallResult } from "../output/formatter.mjs";
|
|
6
7
|
import { generateSnippet } from "../output/codegen.mjs";
|
|
@@ -14,6 +15,7 @@ import { createSpinner } from "../output/spinner.mjs";
|
|
|
14
15
|
export async function runInteractive(flags) {
|
|
15
16
|
const apiKey = resolveApiKey(flags.apiKey);
|
|
16
17
|
const baseUrl = flags.baseUrl;
|
|
18
|
+
const { region, baseUrl: resolvedBaseUrl } = resolveBaseUrl({ baseUrlFlag: baseUrl, apiKey });
|
|
17
19
|
const limit = parseInt(flags.limit, 10) || 5;
|
|
18
20
|
const discoverTimeout = (parseInt(flags.timeout, 10) || 30) * 1000;
|
|
19
21
|
const callTimeout = (parseInt(flags.timeout, 10) || 60) * 1000;
|
|
@@ -63,7 +65,7 @@ export async function runInteractive(flags) {
|
|
|
63
65
|
index: i + 1, tool_id: t.tool_id, name: t.name, provider_name: t.provider_name,
|
|
64
66
|
}));
|
|
65
67
|
// Persist session so index shortcuts work in subsequent non-interactive calls
|
|
66
|
-
writeSession({ discoveryId: result.search_id, query, results: state.results });
|
|
68
|
+
writeSession({ discoveryId: result.search_id, query, region, baseUrl: resolvedBaseUrl, results: state.results });
|
|
67
69
|
console.log(formatDiscoverResult(result));
|
|
68
70
|
break;
|
|
69
71
|
}
|
package/src/commands/login.mjs
CHANGED
|
@@ -5,11 +5,10 @@ import { resolve } from "../config/resolve.mjs";
|
|
|
5
5
|
import { setConfigValue, deleteConfigValue } from "../config/store.mjs";
|
|
6
6
|
import { discoverTools } from "../client/api.mjs";
|
|
7
7
|
import { VERSION } from "../config/defaults.mjs";
|
|
8
|
+
import { resolveBaseUrl, getSiteUrl } from "../config/region.mjs";
|
|
8
9
|
import { bold, green, red, dim, cyan } from "../output/colors.mjs";
|
|
9
10
|
import { printLoginBanner } from "../output/banner.mjs";
|
|
10
11
|
|
|
11
|
-
const ACCOUNT_URL = "https://qveris.ai/account?page=api-keys";
|
|
12
|
-
|
|
13
12
|
function openBrowser(url) {
|
|
14
13
|
const cmds = { darwin: "open", win32: "cmd", linux: "xdg-open" };
|
|
15
14
|
const cmd = cmds[platform()] || "xdg-open";
|
|
@@ -87,9 +86,54 @@ function prompt(question) {
|
|
|
87
86
|
});
|
|
88
87
|
}
|
|
89
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Prompt user to select region interactively.
|
|
91
|
+
* Skipped if region is already determined via --base-url, QVERIS_REGION, or QVERIS_BASE_URL.
|
|
92
|
+
*/
|
|
93
|
+
function promptRegion() {
|
|
94
|
+
return new Promise((pResolve, pReject) => {
|
|
95
|
+
console.log(` ${bold("Select your region / 选择站点区域:")}\n`);
|
|
96
|
+
console.log(` ${cyan("1)")} Global — qveris.ai (International users)`);
|
|
97
|
+
console.log(` ${cyan("2)")} China — qveris.cn (中国大陆用户)\n`);
|
|
98
|
+
|
|
99
|
+
if (!process.stdin.isTTY) {
|
|
100
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
101
|
+
rl.question(" Enter 1 or 2: ", (answer) => { rl.close(); pResolve(answer.trim()); });
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
process.stderr.write(" Enter 1 or 2: ");
|
|
106
|
+
|
|
107
|
+
const cleanup = () => {
|
|
108
|
+
process.stdin.removeListener("data", onData);
|
|
109
|
+
try { process.stdin.setRawMode(false); } catch {}
|
|
110
|
+
process.stdin.pause();
|
|
111
|
+
};
|
|
112
|
+
const onExit = () => { try { process.stdin.setRawMode(false); } catch {} };
|
|
113
|
+
process.once("exit", onExit);
|
|
114
|
+
|
|
115
|
+
process.stdin.setRawMode(true);
|
|
116
|
+
process.stdin.resume();
|
|
117
|
+
process.stdin.setEncoding("utf-8");
|
|
118
|
+
|
|
119
|
+
const onData = (chunk) => {
|
|
120
|
+
const ch = chunk[0];
|
|
121
|
+
if (ch === "\x03") { cleanup(); process.removeListener("exit", onExit); process.stderr.write("\n"); pReject(new Error("Aborted")); return; }
|
|
122
|
+
if (ch === "1" || ch === "2") {
|
|
123
|
+
cleanup();
|
|
124
|
+
process.removeListener("exit", onExit);
|
|
125
|
+
process.stderr.write(`${ch}\n`);
|
|
126
|
+
pResolve(ch);
|
|
127
|
+
}
|
|
128
|
+
// Ignore other keys
|
|
129
|
+
};
|
|
130
|
+
process.stdin.on("data", onData);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
90
134
|
export async function runLogin(flags) {
|
|
91
135
|
if (flags.token) {
|
|
92
|
-
await validateAndSave(flags.token);
|
|
136
|
+
await validateAndSave(flags.token, flags.baseUrl);
|
|
93
137
|
return;
|
|
94
138
|
}
|
|
95
139
|
|
|
@@ -97,10 +141,29 @@ export async function runLogin(flags) {
|
|
|
97
141
|
printLoginBanner({ version: VERSION, noColor: flags.noColor });
|
|
98
142
|
}
|
|
99
143
|
|
|
100
|
-
|
|
144
|
+
// Determine region: if already set via flag/env, use that; otherwise ask the user.
|
|
145
|
+
const { region: presetRegion, baseUrl: presetBaseUrl, source: regionSource } = resolveBaseUrl({ baseUrlFlag: flags.baseUrl });
|
|
146
|
+
let region;
|
|
147
|
+
let baseUrl = presetBaseUrl;
|
|
148
|
+
if (regionSource !== "default") {
|
|
149
|
+
// Region explicitly configured — use it directly
|
|
150
|
+
region = presetRegion;
|
|
151
|
+
} else {
|
|
152
|
+
// No explicit region — let user choose interactively
|
|
153
|
+
let choice;
|
|
154
|
+
try {
|
|
155
|
+
choice = await promptRegion();
|
|
156
|
+
} catch {
|
|
157
|
+
return; // Ctrl+C
|
|
158
|
+
}
|
|
159
|
+
region = choice === "2" ? "cn" : "global";
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const accountUrl = `${getSiteUrl(region, baseUrl)}/account?page=api-keys`;
|
|
163
|
+
console.log(`\n Get your API key at: ${cyan(accountUrl)}\n`);
|
|
101
164
|
|
|
102
165
|
if (!flags.noBrowser) {
|
|
103
|
-
openBrowser(
|
|
166
|
+
openBrowser(accountUrl);
|
|
104
167
|
}
|
|
105
168
|
|
|
106
169
|
let key;
|
|
@@ -116,18 +179,21 @@ export async function runLogin(flags) {
|
|
|
116
179
|
return;
|
|
117
180
|
}
|
|
118
181
|
|
|
119
|
-
await validateAndSave(key);
|
|
182
|
+
await validateAndSave(key, flags.baseUrl);
|
|
120
183
|
}
|
|
121
184
|
|
|
122
|
-
async function validateAndSave(key) {
|
|
185
|
+
async function validateAndSave(key, baseUrlFlag) {
|
|
123
186
|
process.stderr.write(` Validating key...`);
|
|
124
187
|
|
|
188
|
+
const { baseUrl, region, source } = resolveBaseUrl({ baseUrlFlag, apiKey: key });
|
|
189
|
+
|
|
125
190
|
try {
|
|
126
|
-
await discoverTools({ apiKey: key, query: "test", limit: 1, timeoutMs: 10000 });
|
|
191
|
+
await discoverTools({ apiKey: key, baseUrl, query: "test", limit: 1, timeoutMs: 10000 });
|
|
127
192
|
setConfigValue("api_key", key);
|
|
128
193
|
const masked = key.slice(0, 6) + "..." + key.slice(-4);
|
|
129
194
|
console.error(`\r\x1b[K`);
|
|
130
195
|
console.log(` ${green("\u2713")} Authenticated as ${bold(masked)}`);
|
|
196
|
+
console.log(` ${dim("Region:")} ${region} ${dim(`(${source})`)}`);
|
|
131
197
|
console.log(` ${dim("Key saved to config.")}`);
|
|
132
198
|
} catch {
|
|
133
199
|
console.error(`\r\x1b[K`);
|
|
@@ -151,15 +217,17 @@ export async function runWhoami(flags) {
|
|
|
151
217
|
}
|
|
152
218
|
|
|
153
219
|
const masked = key.slice(0, 6) + "..." + key.slice(-4);
|
|
220
|
+
const { baseUrl, region, source: regionSource } = resolveBaseUrl({ baseUrlFlag: flags.baseUrl, apiKey: key });
|
|
154
221
|
|
|
155
222
|
process.stderr.write(` Validating...`);
|
|
156
223
|
|
|
157
224
|
try {
|
|
158
|
-
await discoverTools({ apiKey: key, query: "test", limit: 1, timeoutMs: 10000 });
|
|
225
|
+
await discoverTools({ apiKey: key, baseUrl, query: "test", limit: 1, timeoutMs: 10000 });
|
|
159
226
|
process.stderr.write("\r\x1b[K");
|
|
160
227
|
console.log(`\n ${green("\u2713")} Authenticated`);
|
|
161
228
|
console.log(` Key: ${bold(masked)}`);
|
|
162
229
|
console.log(` Source: ${dim(source)}`);
|
|
230
|
+
console.log(` Region: ${region} ${dim(`(${regionSource})`)}`);
|
|
163
231
|
} catch {
|
|
164
232
|
console.error(`\r\x1b[K`);
|
|
165
233
|
console.log(`\n ${red("\u2718")} Key ${masked} is ${red("invalid")}`);
|
package/src/config/region.mjs
CHANGED
|
@@ -13,6 +13,26 @@ const REGION_URLS = {
|
|
|
13
13
|
cn: "https://qveris.cn/api/v1",
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
+
const SITE_URLS = {
|
|
17
|
+
global: "https://qveris.ai",
|
|
18
|
+
cn: "https://qveris.cn",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get the site URL (not API URL) for a given region.
|
|
23
|
+
* For custom regions, attempts to infer from the base URL domain.
|
|
24
|
+
* @param {string} region
|
|
25
|
+
* @param {string} [baseUrl] - The resolved API base URL (used to infer site for custom regions)
|
|
26
|
+
* @returns {string}
|
|
27
|
+
*/
|
|
28
|
+
export function getSiteUrl(region, baseUrl) {
|
|
29
|
+
if (region === "custom" && baseUrl) {
|
|
30
|
+
if (baseUrl.includes("qveris.cn")) return SITE_URLS.cn;
|
|
31
|
+
if (baseUrl.includes("qveris.ai")) return SITE_URLS.global;
|
|
32
|
+
}
|
|
33
|
+
return SITE_URLS[region] || SITE_URLS.global;
|
|
34
|
+
}
|
|
35
|
+
|
|
16
36
|
/**
|
|
17
37
|
* Detect region from API key prefix.
|
|
18
38
|
* @param {string} apiKey
|
package/src/main.mjs
CHANGED
|
@@ -241,12 +241,18 @@ function printUsage(flags = {}) {
|
|
|
241
241
|
${bold("Global Flags:")}
|
|
242
242
|
--json, -j Output raw JSON
|
|
243
243
|
--api-key <key> Override API key
|
|
244
|
+
--base-url <url> Override API base URL
|
|
244
245
|
--timeout <seconds> Request timeout
|
|
245
246
|
--no-color Disable colors
|
|
246
247
|
--verbose, -v Show request details
|
|
247
248
|
--version, -V Print version
|
|
248
249
|
--help, -h Show help
|
|
249
250
|
|
|
251
|
+
${bold("Environment Variables:")}
|
|
252
|
+
QVERIS_API_KEY API key
|
|
253
|
+
QVERIS_REGION Region override (global | cn)
|
|
254
|
+
QVERIS_BASE_URL Custom API base URL
|
|
255
|
+
|
|
250
256
|
${bold("Examples:")}
|
|
251
257
|
qveris discover "weather forecast API"
|
|
252
258
|
qveris inspect 1
|
|
@@ -254,6 +260,6 @@ function printUsage(flags = {}) {
|
|
|
254
260
|
qveris call 1 --params @params.json --codegen curl
|
|
255
261
|
qveris interactive
|
|
256
262
|
|
|
257
|
-
${dim("https://qveris.ai")}
|
|
263
|
+
${dim("https://qveris.ai (global) / https://qveris.cn (China)")}
|
|
258
264
|
`);
|
|
259
265
|
}
|