@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 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 and key source |
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. To override manually:
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
- # Via environment variable
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 set a custom base URL
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qverisai/cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "QVeris CLI -- discover, inspect, and call 10,000+ capabilities",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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
 
@@ -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("https://qveris.ai/account")}\n`);
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("https://qveris.ai/account")}\n`);
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,
@@ -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
  }
@@ -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
- console.log(` Get your API key at: ${cyan(ACCOUNT_URL)}\n`);
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(ACCOUNT_URL);
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")}`);
@@ -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
  }