@nhonh/qabot 0.2.2 → 0.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nhonh/qabot",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "AI-powered universal QA automation tool. Import any project, AI analyzes and runs tests across all layers.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -9,7 +9,7 @@ export class AIEngine {
9
9
  constructor(config = {}) {
10
10
  this.provider = config.provider || "none";
11
11
  this.model =
12
- config.model || AI_PROVIDER_DEFAULTS[this.provider]?.model || "gpt-4o";
12
+ config.model ?? AI_PROVIDER_DEFAULTS[this.provider]?.model ?? "";
13
13
  this.apiKey = resolveApiKey(config);
14
14
  this.baseUrl =
15
15
  config.baseUrl || AI_PROVIDER_DEFAULTS[this.provider]?.baseUrl || "";
@@ -34,11 +34,15 @@ export class AIEngine {
34
34
  }
35
35
 
36
36
  getProviderInfo() {
37
+ const preview = this.apiKey
38
+ ? this.apiKey.slice(0, 4) + "****" + this.apiKey.slice(-4)
39
+ : "";
37
40
  return {
38
41
  provider: this.provider,
39
42
  model: this.model,
40
43
  baseUrl: this.baseUrl,
41
44
  hasApiKey: !!this.apiKey,
45
+ apiKeyPreview: preview,
42
46
  };
43
47
  }
44
48
 
@@ -185,21 +189,23 @@ export class AIEngine {
185
189
  async callProxy(prompt) {
186
190
  if (!this.baseUrl) {
187
191
  throw new Error(
188
- "Proxy provider requires baseUrl. Set ai.baseUrl in qabot.config.js",
192
+ "Proxy provider requires baseUrl. Set ai.baseUrl in qabot.config.json",
189
193
  );
190
194
  }
191
195
 
192
196
  const headers = this.buildAuthHeaders();
193
197
 
198
+ const body = {
199
+ messages: [{ role: "user", content: prompt }],
200
+ temperature: this.temperature,
201
+ max_tokens: this.maxTokens,
202
+ };
203
+ if (this.model) body.model = this.model;
204
+
194
205
  const res = await fetch(this.baseUrl, {
195
206
  method: "POST",
196
207
  headers,
197
- body: JSON.stringify({
198
- model: this.model,
199
- messages: [{ role: "user", content: prompt }],
200
- temperature: this.temperature,
201
- max_tokens: this.maxTokens,
202
- }),
208
+ body: JSON.stringify(body),
203
209
  });
204
210
  if (!res.ok) {
205
211
  const body = await res.text().catch(() => "");
@@ -43,12 +43,43 @@ function showCurrentConfig(config) {
43
43
  ["Setting", "Value"],
44
44
  [
45
45
  ["Provider", info.provider || "none"],
46
- ["Model", info.model || "-"],
46
+ ["Model", info.model || "(proxy default)"],
47
47
  ["Base URL", info.baseUrl || "(default)"],
48
- ["API Key", info.hasApiKey ? "\u2713 configured" : "\u2717 missing"],
48
+ [
49
+ "API Key",
50
+ info.hasApiKey ? `\u2713 ${info.apiKeyPreview}` : "\u2717 missing",
51
+ ],
52
+ ["Auth Header", engine.authHeader || "(none)"],
53
+ [
54
+ "Key Source",
55
+ ai.apiKey
56
+ ? "config (apiKey)"
57
+ : ai.apiKeyEnv
58
+ ? `env ($${ai.apiKeyEnv})`
59
+ : "auto-detect",
60
+ ],
61
+ ["Available", engine.isAvailable() ? "\u2713 yes" : "\u2717 no"],
49
62
  ],
50
63
  );
51
64
  logger.blank();
65
+
66
+ if (!engine.isAvailable()) {
67
+ logger.warn("AI is not available. To fix:");
68
+ if (info.provider === "proxy") {
69
+ logger.dim(" Option 1: Set apiKey directly in qabot.config.json:");
70
+ logger.dim(' "ai": { "apiKey": "your-key", ... }');
71
+ logger.dim(
72
+ ` Option 2: Set env var: export ${ai.apiKeyEnv || "PROXY_API_KEY"}=your-key`,
73
+ );
74
+ logger.dim(
75
+ " Option 3: Remove auth requirement if proxy doesn't need it:",
76
+ );
77
+ logger.dim(' "ai": { "authHeader": null, ... }');
78
+ } else {
79
+ logger.dim(" Run: qabot auth");
80
+ }
81
+ logger.blank();
82
+ }
52
83
  }
53
84
 
54
85
  async function testConnection(config) {
@@ -60,7 +91,17 @@ async function testConnection(config) {
60
91
  return;
61
92
  }
62
93
 
94
+ const info = engine.getProviderInfo();
63
95
  logger.info(`Testing connection to ${chalk.bold(engine.provider)}...`);
96
+ logger.blank();
97
+ logger.dim(` Provider: ${info.provider}`);
98
+ logger.dim(` Model: ${info.model || "(none)"}`);
99
+ logger.dim(` Base URL: ${info.baseUrl}`);
100
+ logger.dim(
101
+ ` API Key: ${info.hasApiKey ? info.apiKeyPreview : "(none)"}`,
102
+ );
103
+ logger.dim(` Auth Header: ${engine.authHeader || "(none)"}`);
104
+ logger.blank();
64
105
 
65
106
  try {
66
107
  const response = await engine.complete("Reply with exactly: QABOT_OK");
@@ -76,6 +117,30 @@ async function testConnection(config) {
76
117
  }
77
118
  } catch (err) {
78
119
  logger.error(`Connection failed: ${err.message}`);
120
+ logger.blank();
121
+ logger.info("Troubleshooting:");
122
+ if (err.message.includes("401") || err.message.includes("auth")) {
123
+ logger.dim(" 1. Check API key is correct");
124
+ logger.dim(
125
+ ` 2. Verify env var: echo $${ai.apiKeyEnv || "PROXY_API_KEY"}`,
126
+ );
127
+ logger.dim(" 3. Or set apiKey directly in qabot.config.json:");
128
+ logger.dim(' "ai": { "apiKey": "your-key-here", ... }');
129
+ }
130
+ if (
131
+ err.message.includes("ECONNREFUSED") ||
132
+ err.message.includes("connect")
133
+ ) {
134
+ logger.dim(` 1. Check server is running at ${info.baseUrl}`);
135
+ logger.dim(" 2. Verify URL is correct in qabot.config.json");
136
+ }
137
+ if (err.message.includes("500")) {
138
+ logger.dim(" 1. Server error — check proxy/LLM server logs");
139
+ logger.dim(" 2. Model name may be wrong — verify ai.model in config");
140
+ logger.dim(
141
+ ` 3. Try: curl -X POST ${info.baseUrl} -H "Content-Type: application/json" -d '{"messages":[{"role":"user","content":"hi"}]}'`,
142
+ );
143
+ }
79
144
  }
80
145
  }
81
146
 
@@ -147,7 +212,7 @@ async function interactiveSetup(projectDir, config, isEmpty) {
147
212
  ]);
148
213
 
149
214
  aiConfig.baseUrl = proxyAnswers.baseUrl;
150
- if (proxyAnswers.model) aiConfig.model = proxyAnswers.model;
215
+ aiConfig.model = proxyAnswers.model || "";
151
216
 
152
217
  if (proxyAnswers.authMethod === "bearer") {
153
218
  aiConfig.authHeader = "Authorization";
@@ -168,11 +233,8 @@ async function interactiveSetup(projectDir, config, isEmpty) {
168
233
  }
169
234
 
170
235
  if (proxyAnswers.apiKey) {
236
+ aiConfig.apiKey = proxyAnswers.apiKey;
171
237
  aiConfig.apiKeyEnv = "PROXY_API_KEY";
172
- logger.blank();
173
- logger.warn("Store your API key as an environment variable:");
174
- logger.dim(` export PROXY_API_KEY="${proxyAnswers.apiKey}"`);
175
- logger.dim(" Or add to .env file: PROXY_API_KEY=your-key");
176
238
  }
177
239
  } else if (provider === "ollama") {
178
240
  const { baseUrl, model } = await enquirer.prompt([
@@ -218,12 +280,8 @@ async function interactiveSetup(projectDir, config, isEmpty) {
218
280
 
219
281
  aiConfig.model = answers.model;
220
282
  aiConfig.apiKeyEnv = envName;
221
-
222
283
  if (answers.apiKey) {
223
- logger.blank();
224
- logger.warn("Store your API key as an environment variable:");
225
- logger.dim(` export ${envName}="${answers.apiKey}"`);
226
- logger.dim(` Or add to .env file: ${envName}=your-key`);
284
+ aiConfig.apiKey = answers.apiKey;
227
285
  }
228
286
  }
229
287
 
@@ -1,4 +1,4 @@
1
- export const VERSION = "0.2.2";
1
+ export const VERSION = "0.3.0";
2
2
  export const TOOL_NAME = "qabot";
3
3
 
4
4
  export const PROJECT_TYPES = [