ai-consultants 2.10.1

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.
Files changed (62) hide show
  1. package/.claude/commands/ai-consultants:consult.md +94 -0
  2. package/.claude/commands/ai-consultants:debate.md +103 -0
  3. package/.claude/commands/ai-consultants:help.md +30 -0
  4. package/CLAUDE.md +839 -0
  5. package/LICENSE +21 -0
  6. package/README.md +758 -0
  7. package/SKILL.md +156 -0
  8. package/bin/ai-consultants +96 -0
  9. package/docs/COST_RATES.md +172 -0
  10. package/docs/JSON_SCHEMA.md +305 -0
  11. package/docs/SETUP.md +879 -0
  12. package/docs/SMART_ROUTING.md +219 -0
  13. package/docs/cost_rates.json +143 -0
  14. package/package.json +47 -0
  15. package/references/configuration.md +141 -0
  16. package/references/details.md +60 -0
  17. package/scripts/build_context.sh +562 -0
  18. package/scripts/classify_question.sh +148 -0
  19. package/scripts/config.sh +753 -0
  20. package/scripts/configure.sh +1093 -0
  21. package/scripts/consult_all.sh +934 -0
  22. package/scripts/debate_round.sh +421 -0
  23. package/scripts/doctor.sh +642 -0
  24. package/scripts/followup.sh +197 -0
  25. package/scripts/install.sh +319 -0
  26. package/scripts/lib/api.sh +640 -0
  27. package/scripts/lib/api_query.sh +228 -0
  28. package/scripts/lib/cache.sh +338 -0
  29. package/scripts/lib/chunking.sh +880 -0
  30. package/scripts/lib/code_optimizer.sh +671 -0
  31. package/scripts/lib/common.sh +1088 -0
  32. package/scripts/lib/costs.sh +674 -0
  33. package/scripts/lib/personas.sh +372 -0
  34. package/scripts/lib/progress.sh +251 -0
  35. package/scripts/lib/reflection.sh +447 -0
  36. package/scripts/lib/routing.sh +566 -0
  37. package/scripts/lib/schema.json +165 -0
  38. package/scripts/lib/session.sh +327 -0
  39. package/scripts/lib/symbol_map.sh +445 -0
  40. package/scripts/lib/voting.sh +495 -0
  41. package/scripts/peer_review.sh +460 -0
  42. package/scripts/preflight_check.sh +452 -0
  43. package/scripts/query_aider.sh +83 -0
  44. package/scripts/query_amp.sh +67 -0
  45. package/scripts/query_claude.sh +99 -0
  46. package/scripts/query_codex.sh +92 -0
  47. package/scripts/query_cursor.sh +72 -0
  48. package/scripts/query_deepseek.sh +17 -0
  49. package/scripts/query_gemini.sh +85 -0
  50. package/scripts/query_glm.sh +17 -0
  51. package/scripts/query_grok.sh +17 -0
  52. package/scripts/query_kilo.sh +140 -0
  53. package/scripts/query_kimi.sh +67 -0
  54. package/scripts/query_minimax.sh +17 -0
  55. package/scripts/query_mistral.sh +97 -0
  56. package/scripts/query_ollama.sh +328 -0
  57. package/scripts/query_qwen3.sh +93 -0
  58. package/scripts/setup_wizard.sh +367 -0
  59. package/scripts/skillport-install.sh +196 -0
  60. package/scripts/synthesize.sh +332 -0
  61. package/templates/consultation_report.md +323 -0
  62. package/templates/synthesis_prompt.md +120 -0
@@ -0,0 +1,97 @@
1
+ #!/bin/bash
2
+ # query_mistral.sh - Query Mistral Vibe CLI or API
3
+ #
4
+ # Usage: ./query_mistral.sh "question" [context_file] [output_file]
5
+ #
6
+ # Environment variables:
7
+ # MISTRAL_MODEL - Model to use (default: mistral-large-3)
8
+ # MISTRAL_TIMEOUT - Timeout in seconds (default: 180)
9
+ # MISTRAL_USE_API - Use API mode instead of CLI (default: false)
10
+ # MISTRAL_API_KEY - API key for API mode
11
+ # ENABLE_PERSONA - Enable "The Devil's Advocate" persona (default: true)
12
+
13
+ set -euo pipefail
14
+
15
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
16
+ source "$SCRIPT_DIR/lib/common.sh"
17
+ source "$SCRIPT_DIR/lib/personas.sh"
18
+
19
+ # --- Parameters ---
20
+ QUERY="${1:-}"
21
+ CONTEXT_FILE="${2:-}"
22
+ OUTPUT_FILE="${3:-/tmp/mistral_response.json}"
23
+
24
+ # --- Configuration ---
25
+ ENABLE_PERSONA="${ENABLE_PERSONA:-true}"
26
+ CONSULTANT_NAME="Mistral"
27
+
28
+ # --- Build query ---
29
+ FULL_QUERY=$(build_full_query "$QUERY" "$CONTEXT_FILE")
30
+ validate_query "$FULL_QUERY" "Mistral Vibe" || exit 1
31
+
32
+ # --- Add persona if enabled ---
33
+ if [[ "$ENABLE_PERSONA" == "true" ]]; then
34
+ FULL_QUERY=$(build_query_with_persona "$CONSULTANT_NAME" "$FULL_QUERY")
35
+ fi
36
+
37
+ # --- Timestamp for metadata ---
38
+ START_TIME=$(get_timestamp_ms)
39
+
40
+ # --- Execution (CLI or API mode) ---
41
+ TEMP_OUTPUT=$(mktemp)
42
+
43
+ if is_api_mode "mistral"; then
44
+ # --- API Mode ---
45
+ log_api_mode_status "mistral"
46
+ validate_api_mode "mistral" || exit 1
47
+
48
+ source "$SCRIPT_DIR/lib/api_query.sh"
49
+
50
+ run_api_mode_query \
51
+ "$CONSULTANT_NAME" \
52
+ "$MISTRAL_MODEL" \
53
+ "$FULL_QUERY" \
54
+ "$TEMP_OUTPUT" \
55
+ "$MISTRAL_TIMEOUT_SECONDS"
56
+
57
+ exit_code=$?
58
+ else
59
+ # --- CLI Mode ---
60
+ log_api_mode_status "mistral"
61
+ check_command "$MISTRAL_CMD" "Mistral Vibe CLI" "pip install mistral-vibe" || exit 1
62
+
63
+ run_query \
64
+ "Mistral Vibe" \
65
+ "$TEMP_OUTPUT" \
66
+ "$MISTRAL_TIMEOUT_SECONDS" \
67
+ "$MISTRAL_CMD" -p "$FULL_QUERY" --output text < /dev/null
68
+
69
+ exit_code=$?
70
+ fi
71
+
72
+ # --- Calculate latency ---
73
+ END_TIME=$(get_timestamp_ms)
74
+ LATENCY_MS=$((END_TIME - START_TIME))
75
+
76
+ # --- Configuration for response building ---
77
+ MODEL_USED="${MISTRAL_MODEL:-mistral-large-3}"
78
+ PERSONA_NAME=$(get_persona_name "$CONSULTANT_NAME")
79
+
80
+ # --- Post-processing: wrap in full schema using shared helpers ---
81
+ if [[ $exit_code -eq 0 && -f "$TEMP_OUTPUT" && -s "$TEMP_OUTPUT" ]]; then
82
+ RAW_RESPONSE=$(cat "$TEMP_OUTPUT")
83
+ rm -f "$TEMP_OUTPUT"
84
+
85
+ # Try to parse as structured JSON
86
+ if echo "$RAW_RESPONSE" | jq -e '.response.summary' > /dev/null 2>&1; then
87
+ build_structured_response "$CONSULTANT_NAME" "$MODEL_USED" "$PERSONA_NAME" "$RAW_RESPONSE" "$LATENCY_MS" > "$OUTPUT_FILE"
88
+ else
89
+ build_fallback_response "$CONSULTANT_NAME" "$MODEL_USED" "$PERSONA_NAME" "$RAW_RESPONSE" "$LATENCY_MS" > "$OUTPUT_FILE"
90
+ fi
91
+ else
92
+ rm -f "$TEMP_OUTPUT"
93
+ build_error_response "$CONSULTANT_NAME" "$MODEL_USED" "$PERSONA_NAME" "Query failed with exit code $exit_code" "$LATENCY_MS" > "$OUTPUT_FILE"
94
+ fi
95
+
96
+ cat "$OUTPUT_FILE"
97
+ exit $exit_code
@@ -0,0 +1,328 @@
1
+ #!/bin/bash
2
+ # query_ollama.sh - Query local Ollama models (v2.2 with Persona and Confidence)
3
+ #
4
+ # Enables fully local, privacy-preserving AI consultation using Ollama.
5
+ # Zero API costs, no data leaves your machine.
6
+ #
7
+ # Usage: ./query_ollama.sh "question" [context_file] [output_file]
8
+ #
9
+ # Environment variables:
10
+ # OLLAMA_MODEL - Model to use (default: llama3.2)
11
+ # OLLAMA_HOST - Ollama server URL (default: http://localhost:11434)
12
+ # OLLAMA_TIMEOUT - Timeout in seconds (default: 300)
13
+ # ENABLE_PERSONA - Enable "The Local Expert" persona (default: true)
14
+ #
15
+ # Prerequisites:
16
+ # - Ollama installed: curl -fsSL https://ollama.com/install.sh | sh
17
+ # - Model pulled: ollama pull llama3.2
18
+
19
+ set -euo pipefail
20
+
21
+ # Cleanup trap for temp files
22
+ TEMP_OUTPUT=""
23
+ cleanup() {
24
+ [[ -n "$TEMP_OUTPUT" && -f "$TEMP_OUTPUT" ]] && rm -f "$TEMP_OUTPUT"
25
+ }
26
+ trap cleanup EXIT
27
+
28
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
29
+ source "$SCRIPT_DIR/lib/common.sh"
30
+ source "$SCRIPT_DIR/lib/personas.sh"
31
+
32
+ # =============================================================================
33
+ # CONFIGURATION
34
+ # =============================================================================
35
+
36
+ OLLAMA_MODEL="${OLLAMA_MODEL:-llama3.2}"
37
+ OLLAMA_HOST="${OLLAMA_HOST:-http://localhost:11434}"
38
+ OLLAMA_TIMEOUT="${OLLAMA_TIMEOUT:-300}"
39
+ OLLAMA_TEMPERATURE="${OLLAMA_TEMPERATURE:-0.7}"
40
+ ENABLE_PERSONA="${ENABLE_PERSONA:-true}"
41
+ CONSULTANT_NAME="Ollama"
42
+
43
+ # Parameters
44
+ QUERY="${1:-}"
45
+ CONTEXT_FILE="${2:-}"
46
+ OUTPUT_FILE="${3:-/tmp/ollama_response.json}"
47
+
48
+ # =============================================================================
49
+ # PERSONA DEFINITION FOR OLLAMA
50
+ # =============================================================================
51
+
52
+ # Register Ollama persona if not already defined
53
+ OLLAMA_PERSONA_ID="${OLLAMA_PERSONA_ID:-16}"
54
+ OLLAMA_PERSONA="${OLLAMA_PERSONA:-The Local Expert}"
55
+ OLLAMA_PERSONA_PROMPT="${OLLAMA_PERSONA_PROMPT:-You are 'The Local Expert' - a privacy-conscious AI assistant running locally. You prioritize:
56
+ - Practical, implementable solutions
57
+ - Clear explanations without assumptions about external services
58
+ - Security and privacy considerations
59
+ - Efficient use of local resources}"
60
+
61
+ # =============================================================================
62
+ # PREREQUISITE CHECKS
63
+ # =============================================================================
64
+
65
+ # Check if Ollama CLI is available
66
+ if ! command -v ollama &> /dev/null; then
67
+ log_error "Ollama CLI not found"
68
+ log_info "Install with: curl -fsSL https://ollama.com/install.sh | sh"
69
+ exit 1
70
+ fi
71
+
72
+ # Check if Ollama server is running
73
+ check_ollama_server() {
74
+ if ! curl -s "$OLLAMA_HOST/api/tags" > /dev/null 2>&1; then
75
+ log_error "Ollama server not responding at $OLLAMA_HOST"
76
+ log_info "Start with: ollama serve"
77
+ return 1
78
+ fi
79
+ return 0
80
+ }
81
+
82
+ # Check if the requested model is available
83
+ check_model_available() {
84
+ local model="$1"
85
+ local available_models
86
+ available_models=$(curl -s "$OLLAMA_HOST/api/tags" 2>/dev/null | jq -r '.models[].name // empty' 2>/dev/null)
87
+
88
+ if [[ -z "$available_models" ]]; then
89
+ log_warn "Could not verify model availability"
90
+ return 0
91
+ fi
92
+
93
+ # Check if model (or base model) is available
94
+ local base_model="${model%%:*}"
95
+ if echo "$available_models" | grep -q "^${base_model}"; then
96
+ return 0
97
+ fi
98
+
99
+ log_error "Model '$model' not found locally"
100
+ log_info "Pull with: ollama pull $model"
101
+ return 1
102
+ }
103
+
104
+ # Run checks
105
+ check_ollama_server || exit 1
106
+ check_model_available "$OLLAMA_MODEL" || exit 1
107
+
108
+ # =============================================================================
109
+ # BUILD QUERY
110
+ # =============================================================================
111
+
112
+ # --- Build query ---
113
+ FULL_QUERY=$(build_full_query "$QUERY" "$CONTEXT_FILE")
114
+ validate_query "$FULL_QUERY" "Ollama" || exit 1
115
+
116
+ # --- Add persona if enabled ---
117
+ if [[ "$ENABLE_PERSONA" == "true" ]]; then
118
+ PERSONA_PROMPT="$OLLAMA_PERSONA_PROMPT
119
+
120
+ You must respond in this EXACT JSON format:
121
+ {
122
+ \"response\": {
123
+ \"summary\": \"Brief TL;DR (2-3 sentences)\",
124
+ \"detailed\": \"Full detailed response\",
125
+ \"approach\": \"Name of the approach/solution\",
126
+ \"pros\": [\"advantage 1\", \"advantage 2\"],
127
+ \"cons\": [\"disadvantage 1\", \"disadvantage 2\"],
128
+ \"caveats\": [\"important consideration\"]
129
+ },
130
+ \"confidence\": {
131
+ \"score\": 7,
132
+ \"reasoning\": \"Why this confidence level\",
133
+ \"uncertainty_factors\": [\"What could affect this answer\"]
134
+ }
135
+ }
136
+
137
+ IMPORTANT: Respond ONLY with valid JSON, no markdown or additional text.
138
+
139
+ "
140
+ FULL_QUERY="${PERSONA_PROMPT}${FULL_QUERY}"
141
+ fi
142
+
143
+ # =============================================================================
144
+ # EXECUTE QUERY
145
+ # =============================================================================
146
+
147
+ # --- Timestamp for metadata ---
148
+ START_TIME=$(get_timestamp_ms)
149
+
150
+ # --- Build API request body ---
151
+ REQUEST_BODY=$(jq -n \
152
+ --arg model "$OLLAMA_MODEL" \
153
+ --arg prompt "$FULL_QUERY" \
154
+ --argjson temperature "$OLLAMA_TEMPERATURE" \
155
+ '{
156
+ model: $model,
157
+ prompt: $prompt,
158
+ stream: false,
159
+ options: {
160
+ temperature: $temperature
161
+ }
162
+ }')
163
+
164
+ # --- Execute query ---
165
+ log_info "Consulting Ollama ($OLLAMA_MODEL) locally..."
166
+
167
+ TEMP_OUTPUT=$(mktemp)
168
+ HTTP_CODE=$(curl -s -w "%{http_code}" \
169
+ --max-time "$OLLAMA_TIMEOUT" \
170
+ -X POST "$OLLAMA_HOST/api/generate" \
171
+ -H "Content-Type: application/json" \
172
+ -d "$REQUEST_BODY" \
173
+ -o "$TEMP_OUTPUT" 2>/dev/null) || HTTP_CODE="000"
174
+
175
+ exit_code=$?
176
+
177
+ # --- Calculate latency ---
178
+ END_TIME=$(get_timestamp_ms)
179
+ LATENCY_MS=$((END_TIME - START_TIME))
180
+
181
+ # =============================================================================
182
+ # PROCESS RESPONSE
183
+ # =============================================================================
184
+
185
+ if [[ "$HTTP_CODE" == "200" && -f "$TEMP_OUTPUT" && -s "$TEMP_OUTPUT" ]]; then
186
+ # Extract response from Ollama format
187
+ RAW_RESPONSE=$(cat "$TEMP_OUTPUT")
188
+
189
+ # Get the response text and metadata
190
+ INNER_RESPONSE=$(echo "$RAW_RESPONSE" | jq -r '.response // empty' 2>/dev/null)
191
+ TOTAL_DURATION=$(echo "$RAW_RESPONSE" | jq -r '.total_duration // 0' 2>/dev/null)
192
+ EVAL_COUNT=$(echo "$RAW_RESPONSE" | jq -r '.eval_count // 0' 2>/dev/null)
193
+
194
+ if [[ -z "$INNER_RESPONSE" ]]; then
195
+ log_error "Empty response from Ollama"
196
+ exit_code=1
197
+ else
198
+ # Try to parse as JSON (from our structured prompt)
199
+ PARSED_JSON=""
200
+
201
+ # First try: direct JSON parse
202
+ if echo "$INNER_RESPONSE" | jq -e '.response.summary' > /dev/null 2>&1; then
203
+ PARSED_JSON="$INNER_RESPONSE"
204
+ else
205
+ # Second try: extract JSON from text
206
+ EXTRACTED=$(echo "$INNER_RESPONSE" | sed -n '/{/,/}/p' | tr '\n' ' ')
207
+ if [[ -n "$EXTRACTED" ]] && echo "$EXTRACTED" | jq -e '.response.summary' > /dev/null 2>&1; then
208
+ PARSED_JSON="$EXTRACTED"
209
+ fi
210
+ fi
211
+
212
+ if [[ -n "$PARSED_JSON" ]]; then
213
+ # Structured response - wrap with metadata
214
+ jq -n \
215
+ --arg consultant "$CONSULTANT_NAME" \
216
+ --arg model "$OLLAMA_MODEL" \
217
+ --arg persona "$OLLAMA_PERSONA" \
218
+ --argjson inner "$PARSED_JSON" \
219
+ --argjson eval_count "$EVAL_COUNT" \
220
+ --argjson latency "$LATENCY_MS" \
221
+ --arg timestamp "$(date -Iseconds)" \
222
+ '{
223
+ consultant: $consultant,
224
+ model: $model,
225
+ persona: $persona,
226
+ response: $inner.response,
227
+ confidence: $inner.confidence,
228
+ metadata: {
229
+ tokens_used: $eval_count,
230
+ latency_ms: $latency,
231
+ model_version: $model,
232
+ timestamp: $timestamp,
233
+ local: true,
234
+ cost: 0
235
+ }
236
+ }' > "$OUTPUT_FILE"
237
+ else
238
+ # Unstructured response - create base structure
239
+ jq -n \
240
+ --arg consultant "$CONSULTANT_NAME" \
241
+ --arg model "$OLLAMA_MODEL" \
242
+ --arg persona "$OLLAMA_PERSONA" \
243
+ --arg response "$INNER_RESPONSE" \
244
+ --argjson eval_count "$EVAL_COUNT" \
245
+ --argjson latency "$LATENCY_MS" \
246
+ --arg timestamp "$(date -Iseconds)" \
247
+ '{
248
+ consultant: $consultant,
249
+ model: $model,
250
+ persona: $persona,
251
+ response: {
252
+ summary: "See detailed response below",
253
+ detailed: $response,
254
+ approach: "local_inference",
255
+ pros: ["No API costs", "Full privacy", "No rate limits"],
256
+ cons: ["Limited by local hardware"],
257
+ caveats: ["Response format not structured"]
258
+ },
259
+ confidence: {
260
+ score: 6,
261
+ reasoning: "Local model response - confidence estimated",
262
+ uncertainty_factors: ["Unstructured output format"]
263
+ },
264
+ metadata: {
265
+ tokens_used: $eval_count,
266
+ latency_ms: $latency,
267
+ model_version: $model,
268
+ timestamp: $timestamp,
269
+ local: true,
270
+ cost: 0
271
+ }
272
+ }' > "$OUTPUT_FILE"
273
+ fi
274
+
275
+ log_success "[Ollama] Response received (${EVAL_COUNT} tokens, ${LATENCY_MS}ms)"
276
+ rm -f "$TEMP_OUTPUT"
277
+ cat "$OUTPUT_FILE"
278
+ exit 0
279
+ fi
280
+ fi
281
+
282
+ # --- Error handling ---
283
+ ERROR_MSG="Query failed"
284
+ if [[ "$HTTP_CODE" == "000" ]]; then
285
+ ERROR_MSG="Connection failed - is Ollama server running?"
286
+ elif [[ "$HTTP_CODE" != "200" ]]; then
287
+ ERROR_MSG="HTTP error $HTTP_CODE"
288
+ fi
289
+
290
+ log_error "[Ollama] $ERROR_MSG"
291
+
292
+ jq -n \
293
+ --arg consultant "$CONSULTANT_NAME" \
294
+ --arg model "$OLLAMA_MODEL" \
295
+ --arg persona "$OLLAMA_PERSONA" \
296
+ --argjson latency "$LATENCY_MS" \
297
+ --arg timestamp "$(date -Iseconds)" \
298
+ --arg error "$ERROR_MSG" \
299
+ '{
300
+ consultant: $consultant,
301
+ model: $model,
302
+ persona: $persona,
303
+ response: {
304
+ summary: "ERROR: Consultation failed",
305
+ detailed: $error,
306
+ approach: "error",
307
+ pros: [],
308
+ cons: [],
309
+ caveats: []
310
+ },
311
+ confidence: {
312
+ score: 0,
313
+ reasoning: "Consultation failed",
314
+ uncertainty_factors: ["Execution error"]
315
+ },
316
+ metadata: {
317
+ latency_ms: $latency,
318
+ model_version: $model,
319
+ timestamp: $timestamp,
320
+ local: true,
321
+ cost: 0,
322
+ error: $error
323
+ }
324
+ }' > "$OUTPUT_FILE"
325
+
326
+ rm -f "$TEMP_OUTPUT"
327
+ cat "$OUTPUT_FILE"
328
+ exit 1
@@ -0,0 +1,93 @@
1
+ #!/bin/bash
2
+ # query_qwen3.sh - Query Qwen3 via CLI (qwen-code) or HTTP API
3
+ #
4
+ # Usage: ./query_qwen3.sh "question" [context_file] [output_file]
5
+ #
6
+ # Environment variables:
7
+ # QWEN3_MODEL - Model to use (default: qwen3.5-plus)
8
+ # QWEN3_TIMEOUT - Timeout in seconds (default: 180)
9
+ # QWEN3_USE_API - Use API mode instead of CLI (default: false)
10
+ # QWEN3_API_KEY - API key for DashScope API (required for API mode)
11
+ # ENABLE_PERSONA - Enable "The Analyst" persona (default: true)
12
+
13
+ set -euo pipefail
14
+
15
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
16
+ source "$SCRIPT_DIR/lib/common.sh"
17
+ source "$SCRIPT_DIR/lib/personas.sh"
18
+
19
+ # --- Parameters ---
20
+ QUERY="${1:-}"
21
+ CONTEXT_FILE="${2:-}"
22
+ OUTPUT_FILE="${3:-/tmp/qwen3_response.json}"
23
+
24
+ # --- Configuration ---
25
+ ENABLE_PERSONA="${ENABLE_PERSONA:-true}"
26
+ CONSULTANT_NAME="Qwen3"
27
+
28
+ # --- Build query ---
29
+ FULL_QUERY=$(build_full_query "$QUERY" "$CONTEXT_FILE")
30
+ validate_query "$FULL_QUERY" "Qwen3" || exit 1
31
+
32
+ # --- Add persona if enabled ---
33
+ if [[ "$ENABLE_PERSONA" == "true" ]]; then
34
+ FULL_QUERY=$(build_query_with_persona "$CONSULTANT_NAME" "$FULL_QUERY")
35
+ fi
36
+
37
+ # --- Timestamp for metadata ---
38
+ START_TIME=$(get_timestamp_ms)
39
+
40
+ # --- Execution (CLI or API mode) ---
41
+ TEMP_OUTPUT=$(mktemp)
42
+
43
+ if is_api_mode "qwen3"; then
44
+ # --- API Mode (DashScope) ---
45
+ log_api_mode_status "qwen3"
46
+ validate_api_mode "qwen3" || exit 1
47
+
48
+ source "$SCRIPT_DIR/lib/api_query.sh"
49
+
50
+ run_api_mode_query \
51
+ "$CONSULTANT_NAME" \
52
+ "$QWEN3_MODEL" \
53
+ "$FULL_QUERY" \
54
+ "$TEMP_OUTPUT" \
55
+ "$QWEN3_TIMEOUT_SECONDS"
56
+
57
+ exit_code=$?
58
+ else
59
+ # --- CLI Mode (qwen-code) ---
60
+ log_api_mode_status "qwen3"
61
+ check_command "$QWEN3_CMD" "Qwen CLI" "npm install -g @qwen-code/qwen-code@latest" || exit 1
62
+
63
+ # qwen-code uses -p for prompt, reads from stdin with -p -
64
+ # Build command args with optional model
65
+ QWEN_ARGS=("$QWEN3_CMD")
66
+ if [[ -n "${QWEN3_MODEL:-}" ]]; then
67
+ QWEN_ARGS+=("-m" "$QWEN3_MODEL")
68
+ fi
69
+ QWEN_ARGS+=("-p" "-")
70
+
71
+ echo "$FULL_QUERY" | run_query \
72
+ "Qwen3" \
73
+ "$TEMP_OUTPUT" \
74
+ "$QWEN3_TIMEOUT_SECONDS" \
75
+ "${QWEN_ARGS[@]}"
76
+
77
+ exit_code=$?
78
+ fi
79
+
80
+ # --- Calculate latency ---
81
+ END_TIME=$(get_timestamp_ms)
82
+ LATENCY_MS=$((END_TIME - START_TIME))
83
+
84
+ # --- Configuration for response building ---
85
+ MODEL_USED="${QWEN3_MODEL:-qwen3.5-plus}"
86
+ PERSONA_NAME=$(get_persona_name "$CONSULTANT_NAME")
87
+
88
+ # --- Post-processing: use shared helper ---
89
+ process_consultant_response "$CONSULTANT_NAME" "$MODEL_USED" "$PERSONA_NAME" \
90
+ "$TEMP_OUTPUT" "$OUTPUT_FILE" "$exit_code" "$LATENCY_MS"
91
+
92
+ cat "$OUTPUT_FILE"
93
+ exit $exit_code