claude-flow 3.5.69 → 3.5.71

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 (101) hide show
  1. package/package.json +1 -1
  2. package/v3/@claude-flow/cli/dist/src/commands/autopilot.js +1 -1
  3. package/v3/@claude-flow/cli/dist/src/commands/hooks.js +4 -7
  4. package/v3/@claude-flow/cli/dist/src/commands/init.js +0 -1
  5. package/v3/@claude-flow/cli/dist/src/commands/neural.js +1 -0
  6. package/v3/@claude-flow/cli/dist/src/commands/providers.js +228 -96
  7. package/v3/@claude-flow/cli/dist/src/commands/security.js +1 -1
  8. package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-tools.js +35 -1
  9. package/v3/@claude-flow/cli/dist/src/mcp-tools/agentdb-tools.js +81 -0
  10. package/v3/@claude-flow/cli/dist/src/mcp-tools/analyze-tools.js +29 -0
  11. package/v3/@claude-flow/cli/dist/src/mcp-tools/autopilot-tools.js +4 -0
  12. package/v3/@claude-flow/cli/dist/src/mcp-tools/browser-tools.js +146 -0
  13. package/v3/@claude-flow/cli/dist/src/mcp-tools/claims-tools.js +116 -0
  14. package/v3/@claude-flow/cli/dist/src/mcp-tools/config-tools.js +53 -0
  15. package/v3/@claude-flow/cli/dist/src/mcp-tools/coordination-tools.js +31 -0
  16. package/v3/@claude-flow/cli/dist/src/mcp-tools/daa-tools.js +61 -0
  17. package/v3/@claude-flow/cli/dist/src/mcp-tools/embeddings-tools.js +26 -0
  18. package/v3/@claude-flow/cli/dist/src/mcp-tools/github-tools.js +96 -0
  19. package/v3/@claude-flow/cli/dist/src/mcp-tools/guidance-tools.js +21 -0
  20. package/v3/@claude-flow/cli/dist/src/mcp-tools/hive-mind-tools.js +56 -0
  21. package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +176 -0
  22. package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +18 -2
  23. package/v3/@claude-flow/cli/dist/src/mcp-tools/neural-tools.js +51 -0
  24. package/v3/@claude-flow/cli/dist/src/mcp-tools/performance-tools.js +11 -0
  25. package/v3/@claude-flow/cli/dist/src/mcp-tools/ruvllm-tools.js +31 -0
  26. package/v3/@claude-flow/cli/dist/src/mcp-tools/security-tools.js +36 -0
  27. package/v3/@claude-flow/cli/dist/src/mcp-tools/session-tools.js +29 -0
  28. package/v3/@claude-flow/cli/dist/src/mcp-tools/swarm-tools.js +30 -0
  29. package/v3/@claude-flow/cli/dist/src/mcp-tools/system-tools.js +6 -0
  30. package/v3/@claude-flow/cli/dist/src/mcp-tools/task-tools.js +33 -0
  31. package/v3/@claude-flow/cli/dist/src/mcp-tools/terminal-tools.js +31 -0
  32. package/v3/@claude-flow/cli/dist/src/mcp-tools/transfer-tools.js +51 -0
  33. package/v3/@claude-flow/cli/dist/src/mcp-tools/wasm-agent-tools.js +61 -0
  34. package/v3/@claude-flow/cli/dist/src/mcp-tools/workflow-tools.js +82 -0
  35. package/v3/@claude-flow/cli/dist/src/memory/intelligence.d.ts +6 -1
  36. package/v3/@claude-flow/cli/dist/src/memory/intelligence.js +51 -1
  37. package/v3/@claude-flow/cli/package.json +1 -1
  38. package/v3/@claude-flow/guidance/dist/adversarial.d.ts +284 -0
  39. package/v3/@claude-flow/guidance/dist/adversarial.js +572 -0
  40. package/v3/@claude-flow/guidance/dist/analyzer.d.ts +530 -0
  41. package/v3/@claude-flow/guidance/dist/analyzer.js +2518 -0
  42. package/v3/@claude-flow/guidance/dist/artifacts.d.ts +283 -0
  43. package/v3/@claude-flow/guidance/dist/artifacts.js +356 -0
  44. package/v3/@claude-flow/guidance/dist/authority.d.ts +290 -0
  45. package/v3/@claude-flow/guidance/dist/authority.js +558 -0
  46. package/v3/@claude-flow/guidance/dist/capabilities.d.ts +209 -0
  47. package/v3/@claude-flow/guidance/dist/capabilities.js +485 -0
  48. package/v3/@claude-flow/guidance/dist/coherence.d.ts +233 -0
  49. package/v3/@claude-flow/guidance/dist/coherence.js +372 -0
  50. package/v3/@claude-flow/guidance/dist/compiler.d.ts +87 -0
  51. package/v3/@claude-flow/guidance/dist/compiler.js +419 -0
  52. package/v3/@claude-flow/guidance/dist/conformance-kit.d.ts +225 -0
  53. package/v3/@claude-flow/guidance/dist/conformance-kit.js +629 -0
  54. package/v3/@claude-flow/guidance/dist/continue-gate.d.ts +214 -0
  55. package/v3/@claude-flow/guidance/dist/continue-gate.js +353 -0
  56. package/v3/@claude-flow/guidance/dist/crypto-utils.d.ts +17 -0
  57. package/v3/@claude-flow/guidance/dist/crypto-utils.js +24 -0
  58. package/v3/@claude-flow/guidance/dist/evolution.d.ts +282 -0
  59. package/v3/@claude-flow/guidance/dist/evolution.js +500 -0
  60. package/v3/@claude-flow/guidance/dist/gates.d.ts +79 -0
  61. package/v3/@claude-flow/guidance/dist/gates.js +302 -0
  62. package/v3/@claude-flow/guidance/dist/gateway.d.ts +206 -0
  63. package/v3/@claude-flow/guidance/dist/gateway.js +452 -0
  64. package/v3/@claude-flow/guidance/dist/generators.d.ts +153 -0
  65. package/v3/@claude-flow/guidance/dist/generators.js +682 -0
  66. package/v3/@claude-flow/guidance/dist/headless.d.ts +177 -0
  67. package/v3/@claude-flow/guidance/dist/headless.js +342 -0
  68. package/v3/@claude-flow/guidance/dist/hooks.d.ts +109 -0
  69. package/v3/@claude-flow/guidance/dist/hooks.js +347 -0
  70. package/v3/@claude-flow/guidance/dist/index.d.ts +205 -0
  71. package/v3/@claude-flow/guidance/dist/index.js +321 -0
  72. package/v3/@claude-flow/guidance/dist/ledger.d.ts +162 -0
  73. package/v3/@claude-flow/guidance/dist/ledger.js +375 -0
  74. package/v3/@claude-flow/guidance/dist/manifest-validator.d.ts +289 -0
  75. package/v3/@claude-flow/guidance/dist/manifest-validator.js +838 -0
  76. package/v3/@claude-flow/guidance/dist/memory-gate.d.ts +222 -0
  77. package/v3/@claude-flow/guidance/dist/memory-gate.js +382 -0
  78. package/v3/@claude-flow/guidance/dist/meta-governance.d.ts +265 -0
  79. package/v3/@claude-flow/guidance/dist/meta-governance.js +348 -0
  80. package/v3/@claude-flow/guidance/dist/optimizer.d.ts +104 -0
  81. package/v3/@claude-flow/guidance/dist/optimizer.js +329 -0
  82. package/v3/@claude-flow/guidance/dist/persistence.d.ts +189 -0
  83. package/v3/@claude-flow/guidance/dist/persistence.js +464 -0
  84. package/v3/@claude-flow/guidance/dist/proof.d.ts +185 -0
  85. package/v3/@claude-flow/guidance/dist/proof.js +238 -0
  86. package/v3/@claude-flow/guidance/dist/retriever.d.ts +116 -0
  87. package/v3/@claude-flow/guidance/dist/retriever.js +394 -0
  88. package/v3/@claude-flow/guidance/dist/ruvbot-integration.d.ts +370 -0
  89. package/v3/@claude-flow/guidance/dist/ruvbot-integration.js +738 -0
  90. package/v3/@claude-flow/guidance/dist/temporal.d.ts +426 -0
  91. package/v3/@claude-flow/guidance/dist/temporal.js +658 -0
  92. package/v3/@claude-flow/guidance/dist/trust.d.ts +283 -0
  93. package/v3/@claude-flow/guidance/dist/trust.js +473 -0
  94. package/v3/@claude-flow/guidance/dist/truth-anchors.d.ts +276 -0
  95. package/v3/@claude-flow/guidance/dist/truth-anchors.js +488 -0
  96. package/v3/@claude-flow/guidance/dist/types.d.ts +378 -0
  97. package/v3/@claude-flow/guidance/dist/types.js +10 -0
  98. package/v3/@claude-flow/guidance/dist/uncertainty.d.ts +372 -0
  99. package/v3/@claude-flow/guidance/dist/uncertainty.js +619 -0
  100. package/v3/@claude-flow/guidance/dist/wasm-kernel.d.ts +48 -0
  101. package/v3/@claude-flow/guidance/dist/wasm-kernel.js +158 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-flow",
3
- "version": "3.5.69",
3
+ "version": "3.5.71",
4
4
  "description": "Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -287,7 +287,7 @@ const predictCommand = {
287
287
  else {
288
288
  output.writeln(`Action: ${prediction?.action || 'unknown'}`);
289
289
  output.writeln(`Confidence: ${prediction?.confidence || 0}`);
290
- if (prediction?.alternatives?.length > 0)
290
+ if (prediction?.alternatives && prediction.alternatives.length > 0)
291
291
  output.writeln(`Alternatives: ${prediction.alternatives.join(', ')}`);
292
292
  }
293
293
  return { success: true };
@@ -3763,7 +3763,7 @@ const postBashCommand = {
3763
3763
  // Token Optimizer command - integrates agentic-flow Agent Booster
3764
3764
  const tokenOptimizeCommand = {
3765
3765
  name: 'token-optimize',
3766
- description: 'Token optimization via agentic-flow Agent Booster (30-50% savings)',
3766
+ description: 'Token optimization via agentic-flow Agent Booster integration',
3767
3767
  options: [
3768
3768
  { name: 'query', short: 'q', type: 'string', description: 'Query for compact context retrieval' },
3769
3769
  { name: 'agents', short: 'A', type: 'number', description: 'Agent count for optimal config', default: '6' },
@@ -3791,7 +3791,6 @@ const tokenOptimizeCommand = {
3791
3791
  memoriesRetrieved: 0,
3792
3792
  };
3793
3793
  let agenticFlowAvailable = false;
3794
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
3795
3794
  let reasoningBank = null;
3796
3795
  try {
3797
3796
  // Check if agentic-flow v3 is available
@@ -3846,10 +3845,8 @@ const tokenOptimizeCommand = {
3846
3845
  output.writeln();
3847
3846
  output.printInfo('ReasoningBank not available - query skipped');
3848
3847
  }
3849
- // Simulate some token savings for demo
3850
- stats.totalTokensSaved += 200;
3851
- stats.cacheHits = 2;
3852
- stats.cacheMisses = 1;
3848
+ // Note: stats reflect only actual measured values from this session.
3849
+ // No simulated/fabricated data is added.
3853
3850
  // Show stats
3854
3851
  if (showStats || showReport) {
3855
3852
  output.writeln();
@@ -4413,7 +4410,7 @@ export const hooksCommand = {
4413
4410
  `${output.highlight('coverage-route')} - Route tasks based on coverage gaps (ruvector)`,
4414
4411
  `${output.highlight('coverage-suggest')}- Suggest coverage improvements`,
4415
4412
  `${output.highlight('coverage-gaps')} - List all coverage gaps with agents`,
4416
- `${output.highlight('token-optimize')} - Token optimization (30-50% savings)`,
4413
+ `${output.highlight('token-optimize')} - Token optimization (agentic-flow integration)`,
4417
4414
  `${output.highlight('model-route')} - Route to optimal model (haiku/sonnet/opus)`,
4418
4415
  `${output.highlight('model-outcome')} - Record model routing outcome`,
4419
4416
  `${output.highlight('model-stats')} - View model routing statistics`,
@@ -18,7 +18,6 @@ async function initCodexAction(ctx, options) {
18
18
  const spinner = output.createSpinner({ text: 'Initializing Codex project...' });
19
19
  spinner.start();
20
20
  try {
21
- // Dynamic import of the Codex initializer with lazy loading fallback
22
21
  let CodexInitializer;
23
22
  // Try multiple resolution strategies for the @claude-flow/codex package
24
23
  // Use a variable to prevent TypeScript from statically resolving the optional module
@@ -1333,6 +1333,7 @@ const benchmarkCommand = {
1333
1333
  const spinner = output.createSpinner({ text: 'Running benchmarks...', spinner: 'dots' });
1334
1334
  spinner.start();
1335
1335
  try {
1336
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- dynamic import of optional native WASM module with no type declarations
1336
1337
  const attention = await import('@ruvector/attention');
1337
1338
  // Manual benchmark since benchmarkAttention has a binding bug
1338
1339
  const benchmarkMechanism = async (name, mechanism) => {
@@ -6,6 +6,91 @@
6
6
  */
7
7
  import { output } from '../output.js';
8
8
  import { configManager } from '../services/config-file-manager.js';
9
+ const PROVIDER_CATALOG = [
10
+ { name: 'Anthropic', type: 'LLM', models: 'claude-3.5-sonnet, opus', envVar: 'ANTHROPIC_API_KEY', configName: 'anthropic' },
11
+ { name: 'OpenAI', type: 'LLM', models: 'gpt-4o, gpt-4-turbo', envVar: 'OPENAI_API_KEY', configName: 'openai' },
12
+ { name: 'OpenAI', type: 'Embedding', models: 'text-embedding-3-small/large', envVar: 'OPENAI_API_KEY', configName: 'openai' },
13
+ { name: 'Google', type: 'LLM', models: 'gemini-pro, gemini-ultra', envVar: 'GOOGLE_API_KEY', configName: 'google' },
14
+ { name: 'Transformers.js', type: 'Embedding', models: 'Xenova/all-MiniLM-L6-v2' },
15
+ { name: 'Agentic Flow', type: 'Embedding', models: 'ONNX optimized' },
16
+ { name: 'Mock', type: 'All', models: 'mock-*' },
17
+ ];
18
+ /**
19
+ * Resolve the API key for a provider by checking the config file first,
20
+ * then falling back to well-known environment variables.
21
+ */
22
+ function resolveApiKey(providerName, configuredProviders) {
23
+ // Check config file entry
24
+ const entry = configuredProviders.find((p) => typeof p.name === 'string' && p.name.toLowerCase() === providerName.toLowerCase());
25
+ if (entry?.apiKey && typeof entry.apiKey === 'string') {
26
+ return entry.apiKey;
27
+ }
28
+ // Check environment variable
29
+ const envMapping = {
30
+ anthropic: 'ANTHROPIC_API_KEY',
31
+ openai: 'OPENAI_API_KEY',
32
+ google: 'GOOGLE_API_KEY',
33
+ };
34
+ const envVar = envMapping[providerName.toLowerCase()];
35
+ if (envVar && process.env[envVar]) {
36
+ return process.env[envVar];
37
+ }
38
+ return undefined;
39
+ }
40
+ /**
41
+ * Make a lightweight HTTP request to verify provider API key validity.
42
+ * Uses a 5-second timeout. Returns { ok, reason }.
43
+ */
44
+ async function testProviderConnectivity(providerName, apiKey) {
45
+ const endpoints = {
46
+ anthropic: {
47
+ url: 'https://api.anthropic.com/v1/models',
48
+ headers: {
49
+ 'x-api-key': apiKey,
50
+ 'anthropic-version': '2023-06-01',
51
+ },
52
+ },
53
+ openai: {
54
+ url: 'https://api.openai.com/v1/models',
55
+ headers: {
56
+ 'Authorization': `Bearer ${apiKey}`,
57
+ },
58
+ },
59
+ google: {
60
+ url: `https://generativelanguage.googleapis.com/v1beta/models?key=${apiKey}`,
61
+ headers: {},
62
+ },
63
+ };
64
+ const endpointConfig = endpoints[providerName.toLowerCase()];
65
+ if (!endpointConfig) {
66
+ return { ok: false, reason: 'No test endpoint available for this provider' };
67
+ }
68
+ try {
69
+ const controller = new AbortController();
70
+ const timeout = setTimeout(() => controller.abort(), 5000);
71
+ const res = await fetch(endpointConfig.url, {
72
+ method: 'GET',
73
+ headers: endpointConfig.headers,
74
+ signal: controller.signal,
75
+ });
76
+ clearTimeout(timeout);
77
+ if (res.ok || res.status === 200) {
78
+ return { ok: true, reason: 'Connected successfully' };
79
+ }
80
+ if (res.status === 401 || res.status === 403) {
81
+ return { ok: false, reason: `Authentication failed (HTTP ${res.status})` };
82
+ }
83
+ // A non-auth error but the server responded — key format may be fine
84
+ return { ok: false, reason: `Unexpected response (HTTP ${res.status})` };
85
+ }
86
+ catch (err) {
87
+ if (err instanceof Error && err.name === 'AbortError') {
88
+ return { ok: false, reason: 'Connection timed out (5s)' };
89
+ }
90
+ const msg = err instanceof Error ? err.message : String(err);
91
+ return { ok: false, reason: `Connection failed: ${msg}` };
92
+ }
93
+ }
9
94
  // List subcommand
10
95
  const listCommand = {
11
96
  name: 'list',
@@ -20,26 +105,88 @@ const listCommand = {
20
105
  ],
21
106
  action: async (ctx) => {
22
107
  const type = ctx.flags.type || 'all';
23
- // Note: Static provider catalog — does not reflect user's configured providers
108
+ const activeOnly = ctx.flags.active;
109
+ // Load user configuration
110
+ const cwd = process.cwd();
111
+ const config = configManager.getConfig(cwd);
112
+ const agents = (config.agents ?? {});
113
+ const configuredProviders = (agents.providers ?? []);
114
+ // Build table rows from the catalog, enriched with configuration status
115
+ const rows = [];
116
+ for (const entry of PROVIDER_CATALOG) {
117
+ // Apply type filter
118
+ if (type !== 'all' && entry.type.toLowerCase() !== type.toLowerCase()) {
119
+ continue;
120
+ }
121
+ let status;
122
+ let keySource = '';
123
+ if (entry.configName) {
124
+ const apiKey = resolveApiKey(entry.configName, configuredProviders);
125
+ if (apiKey) {
126
+ // Determine the source for the key
127
+ const configEntry = configuredProviders.find((p) => typeof p.name === 'string' && p.name.toLowerCase() === entry.configName.toLowerCase());
128
+ if (configEntry?.apiKey && typeof configEntry.apiKey === 'string') {
129
+ keySource = 'config';
130
+ }
131
+ else {
132
+ keySource = 'env';
133
+ }
134
+ status = output.success(`Configured (${keySource})`);
135
+ }
136
+ else {
137
+ status = output.warning('Not configured');
138
+ }
139
+ }
140
+ else if (entry.name === 'Mock') {
141
+ status = output.dim('Dev only');
142
+ }
143
+ else {
144
+ // Local-only providers (Transformers.js, Agentic Flow) — always available
145
+ status = output.success('Available (local)');
146
+ }
147
+ if (activeOnly && !status.includes('Configured') && !status.includes('Available')) {
148
+ continue;
149
+ }
150
+ rows.push({
151
+ provider: entry.name,
152
+ type: entry.type,
153
+ models: entry.models,
154
+ status,
155
+ });
156
+ }
157
+ // Also show any providers in config that are not in the static catalog
158
+ for (const cp of configuredProviders) {
159
+ const cpName = cp.name || '';
160
+ const alreadyListed = PROVIDER_CATALOG.some((e) => e.configName?.toLowerCase() === cpName.toLowerCase() || e.name.toLowerCase() === cpName.toLowerCase());
161
+ if (!alreadyListed && cpName) {
162
+ const hasKey = !!(cp.apiKey || resolveApiKey(cpName, configuredProviders));
163
+ rows.push({
164
+ provider: cpName,
165
+ type: cp.type || 'Custom',
166
+ models: cp.model || output.dim('(not specified)'),
167
+ status: hasKey ? output.success('Configured (config)') : output.warning('Not configured'),
168
+ });
169
+ }
170
+ }
24
171
  output.writeln();
25
- output.writeln(output.bold('Available Providers'));
172
+ output.writeln(output.bold('Providers'));
26
173
  output.writeln(output.dim('─'.repeat(60)));
27
- output.printTable({
28
- columns: [
29
- { key: 'provider', header: 'Provider', width: 18 },
30
- { key: 'type', header: 'Type', width: 12 },
31
- { key: 'models', header: 'Models', width: 25 },
32
- { key: 'status', header: 'Status', width: 12 },
33
- ],
34
- data: [
35
- { provider: 'Anthropic', type: 'LLM', models: 'claude-3.5-sonnet, opus', status: output.success('Active') },
36
- { provider: 'OpenAI', type: 'LLM', models: 'gpt-4o, gpt-4-turbo', status: output.success('Active') },
37
- { provider: 'OpenAI', type: 'Embedding', models: 'text-embedding-3-small/large', status: output.success('Active') },
38
- { provider: 'Transformers.js', type: 'Embedding', models: 'Xenova/all-MiniLM-L6-v2', status: output.success('Active') },
39
- { provider: 'Agentic Flow', type: 'Embedding', models: 'ONNX optimized', status: output.success('Active') },
40
- { provider: 'Mock', type: 'All', models: 'mock-*', status: output.dim('Dev only') },
41
- ],
42
- });
174
+ if (rows.length === 0) {
175
+ output.writeln(output.dim(' No providers match the current filter.'));
176
+ }
177
+ else {
178
+ output.printTable({
179
+ columns: [
180
+ { key: 'provider', header: 'Provider', width: 18 },
181
+ { key: 'type', header: 'Type', width: 12 },
182
+ { key: 'models', header: 'Models', width: 25 },
183
+ { key: 'status', header: 'Status', width: 20 },
184
+ ],
185
+ data: rows,
186
+ });
187
+ }
188
+ output.writeln();
189
+ output.writeln(output.dim('Tip: Use "providers configure -p <name> -k <key>" to set API keys.'));
43
190
  return { success: true };
44
191
  },
45
192
  };
@@ -133,98 +280,83 @@ const testCommand = {
133
280
  const config = configManager.getConfig(cwd);
134
281
  const agents = (config.agents ?? {});
135
282
  const configuredProviders = (agents.providers ?? []);
136
- const getConfigApiKey = (name) => {
137
- const entry = configuredProviders.find((p) => typeof p.name === 'string' && p.name.toLowerCase() === name.toLowerCase());
138
- return entry?.apiKey;
139
- };
140
- const knownChecks = [
141
- {
142
- name: 'Anthropic',
143
- test: async () => {
144
- const key = process.env.ANTHROPIC_API_KEY || getConfigApiKey('anthropic');
145
- if (key)
146
- return { pass: true, reason: 'API key found' };
147
- return { pass: false, reason: 'ANTHROPIC_API_KEY not set and no apiKey in config' };
148
- },
149
- },
150
- {
151
- name: 'OpenAI',
152
- test: async () => {
153
- const key = process.env.OPENAI_API_KEY || getConfigApiKey('openai');
154
- if (key)
155
- return { pass: true, reason: 'API key found' };
156
- return { pass: false, reason: 'OPENAI_API_KEY not set and no apiKey in config' };
157
- },
158
- },
159
- {
160
- name: 'Google',
161
- test: async () => {
162
- const key = process.env.GOOGLE_API_KEY || getConfigApiKey('google');
163
- if (key)
164
- return { pass: true, reason: 'API key found' };
165
- return { pass: false, reason: 'GOOGLE_API_KEY not set and no apiKey in config' };
166
- },
167
- },
168
- {
169
- name: 'Ollama',
170
- test: async () => {
171
- const entry = configuredProviders.find((p) => typeof p.name === 'string' && p.name.toLowerCase() === 'ollama');
172
- const baseUrl = entry?.baseUrl || 'http://localhost:11434';
173
- try {
174
- const controller = new AbortController();
175
- const timeout = setTimeout(() => controller.abort(), 3000);
176
- const res = await fetch(baseUrl, { signal: controller.signal });
177
- clearTimeout(timeout);
178
- if (res.ok)
179
- return { pass: true, reason: `Reachable at ${baseUrl}` };
180
- return { pass: false, reason: `HTTP ${res.status} from ${baseUrl}` };
181
- }
182
- catch {
183
- return { pass: false, reason: `Unreachable at ${baseUrl}` };
184
- }
185
- },
186
- },
283
+ const knownTargets = [
284
+ { name: 'Anthropic', configName: 'anthropic' },
285
+ { name: 'OpenAI', configName: 'openai' },
286
+ { name: 'Google', configName: 'google' },
187
287
  ];
188
- // Filter to requested provider or test all
189
- let checksToRun;
288
+ // Add Ollama as a special case (endpoint-based, no API key)
289
+ const ollamaEntry = configuredProviders.find((p) => typeof p.name === 'string' && p.name.toLowerCase() === 'ollama');
290
+ let targets;
190
291
  if (testAll || !provider) {
191
- checksToRun = knownChecks;
292
+ targets = [...knownTargets];
192
293
  }
193
294
  else {
194
- const match = knownChecks.find((c) => c.name.toLowerCase() === provider.toLowerCase());
195
- if (match) {
196
- checksToRun = [match];
295
+ const match = knownTargets.find((t) => t.name.toLowerCase() === provider.toLowerCase() || t.configName === provider.toLowerCase());
296
+ targets = match ? [match] : [{ name: provider, configName: provider.toLowerCase() }];
297
+ }
298
+ const results = [];
299
+ // Test API-key-based providers with real connectivity checks
300
+ for (const target of targets) {
301
+ const apiKey = resolveApiKey(target.configName, configuredProviders);
302
+ if (!apiKey) {
303
+ results.push({ name: target.name, pass: false, reason: 'Not configured (no API key found)' });
304
+ continue;
197
305
  }
198
- else {
199
- // Unknown provider -- check if it has a config entry with an apiKey
200
- checksToRun = [
201
- {
202
- name: provider,
203
- test: async () => {
204
- const key = getConfigApiKey(provider);
205
- if (key)
206
- return { pass: true, reason: 'API key found in config' };
207
- return { pass: false, reason: 'No API key in environment or config' };
208
- },
209
- },
210
- ];
306
+ output.writeln(output.dim(` Testing ${target.name}...`));
307
+ const result = await testProviderConnectivity(target.name, apiKey);
308
+ results.push({ name: target.name, pass: result.ok, reason: result.reason });
309
+ }
310
+ // Test Ollama separately (endpoint-based, no API key needed)
311
+ if (testAll || !provider || provider.toLowerCase() === 'ollama') {
312
+ const baseUrl = ollamaEntry?.baseUrl || 'http://localhost:11434';
313
+ output.writeln(output.dim(` Testing Ollama at ${baseUrl}...`));
314
+ try {
315
+ const controller = new AbortController();
316
+ const timeout = setTimeout(() => controller.abort(), 5000);
317
+ const res = await fetch(baseUrl, { signal: controller.signal });
318
+ clearTimeout(timeout);
319
+ if (res.ok) {
320
+ results.push({ name: 'Ollama', pass: true, reason: `Connected at ${baseUrl}` });
321
+ }
322
+ else {
323
+ results.push({ name: 'Ollama', pass: false, reason: `HTTP ${res.status} from ${baseUrl}` });
324
+ }
325
+ }
326
+ catch {
327
+ results.push({ name: 'Ollama', pass: false, reason: `Unreachable at ${baseUrl}` });
211
328
  }
212
329
  }
213
- let anyPassed = false;
214
- const results = [];
215
- for (const check of checksToRun) {
216
- const result = await check.test();
217
- results.push({ name: check.name, ...result });
218
- if (result.pass)
219
- anyPassed = true;
330
+ // Also test any custom providers from config that were not in the known list
331
+ if (testAll || !provider) {
332
+ for (const cp of configuredProviders) {
333
+ const cpName = cp.name || '';
334
+ const alreadyTested = results.some((r) => r.name.toLowerCase() === cpName.toLowerCase());
335
+ if (alreadyTested || !cpName)
336
+ continue;
337
+ const apiKey = resolveApiKey(cpName, configuredProviders);
338
+ if (!apiKey) {
339
+ results.push({ name: cpName, pass: false, reason: 'No API key found' });
340
+ }
341
+ else {
342
+ // For custom providers we can only verify the key exists
343
+ results.push({ name: cpName, pass: true, reason: 'API key found (no test endpoint available)' });
344
+ }
345
+ }
220
346
  }
347
+ let anyPassed = false;
221
348
  output.writeln();
222
349
  for (const r of results) {
223
350
  const icon = r.pass ? output.success('PASS') : output.error('FAIL');
224
351
  output.writeln(` ${icon} ${r.name}: ${r.reason}`);
352
+ if (r.pass)
353
+ anyPassed = true;
225
354
  }
226
355
  output.writeln();
227
- if (anyPassed) {
356
+ if (results.length === 0) {
357
+ output.writeln(output.warning('No providers to test. Use "providers configure" to add providers.'));
358
+ }
359
+ else if (anyPassed) {
228
360
  output.writeln(output.success(`${results.filter((r) => r.pass).length}/${results.length} provider(s) passed.`));
229
361
  }
230
362
  else {
@@ -53,7 +53,7 @@ const scanCommand = {
53
53
  }
54
54
  catch (auditErr) {
55
55
  // npm audit exits non-zero when vulnerabilities found — stdout still has JSON
56
- auditResult = auditErr.stdout || '{}';
56
+ auditResult = (auditErr instanceof Error && 'stdout' in auditErr ? auditErr.stdout : undefined) || '{}';
57
57
  }
58
58
  try {
59
59
  const audit = JSON.parse(auditResult);
@@ -7,7 +7,7 @@
7
7
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
8
8
  import { join } from 'node:path';
9
9
  import { getProjectCwd } from './types.js';
10
- import { validateAgentSpawn } from './validate-input.js';
10
+ import { validateIdentifier, validateAgentSpawn } from './validate-input.js';
11
11
  // Storage paths
12
12
  const STORAGE_DIR = '.claude-flow';
13
13
  const AGENT_DIR = 'agents';
@@ -221,6 +221,9 @@ export const agentTools = [
221
221
  required: ['agentId'],
222
222
  },
223
223
  handler: async (input) => {
224
+ const v = validateIdentifier(input.agentId, 'agentId');
225
+ if (!v.valid)
226
+ return { success: false, error: `Input validation failed: ${v.error}` };
224
227
  const store = loadAgentStore();
225
228
  const agentId = input.agentId;
226
229
  if (store.agents[agentId]) {
@@ -252,6 +255,9 @@ export const agentTools = [
252
255
  required: ['agentId'],
253
256
  },
254
257
  handler: async (input) => {
258
+ const v = validateIdentifier(input.agentId, 'agentId');
259
+ if (!v.valid)
260
+ return { agentId: input.agentId, status: 'not_found', error: `Input validation failed: ${v.error}` };
255
261
  const store = loadAgentStore();
256
262
  const agentId = input.agentId;
257
263
  const agent = store.agents[agentId];
@@ -287,6 +293,16 @@ export const agentTools = [
287
293
  },
288
294
  },
289
295
  handler: async (input) => {
296
+ if (input.status) {
297
+ const v = validateIdentifier(input.status, 'status');
298
+ if (!v.valid)
299
+ return { agents: [], total: 0, error: `Input validation failed: ${v.error}` };
300
+ }
301
+ if (input.domain) {
302
+ const v = validateIdentifier(input.domain, 'domain');
303
+ if (!v.valid)
304
+ return { agents: [], total: 0, error: `Input validation failed: ${v.error}` };
305
+ }
290
306
  const store = loadAgentStore();
291
307
  let agents = Object.values(store.agents);
292
308
  // Filter by status
@@ -333,6 +349,11 @@ export const agentTools = [
333
349
  required: ['action'],
334
350
  },
335
351
  handler: async (input) => {
352
+ if (input.agentType) {
353
+ const v = validateIdentifier(input.agentType, 'agentType');
354
+ if (!v.valid)
355
+ return { action: input.action, error: `Input validation failed: ${v.error}` };
356
+ }
336
357
  const store = loadAgentStore();
337
358
  const agents = Object.values(store.agents).filter(a => a.status !== 'terminated');
338
359
  const action = input.action || 'status'; // Default to status
@@ -443,6 +464,11 @@ export const agentTools = [
443
464
  },
444
465
  },
445
466
  handler: async (input) => {
467
+ if (input.agentId) {
468
+ const v = validateIdentifier(input.agentId, 'agentId');
469
+ if (!v.valid)
470
+ return { agentId: input.agentId, error: `Input validation failed: ${v.error}` };
471
+ }
446
472
  const store = loadAgentStore();
447
473
  const agents = Object.values(store.agents).filter(a => a.status !== 'terminated');
448
474
  const threshold = input.threshold || 0.5;
@@ -517,6 +543,14 @@ export const agentTools = [
517
543
  required: ['agentId'],
518
544
  },
519
545
  handler: async (input) => {
546
+ const v = validateIdentifier(input.agentId, 'agentId');
547
+ if (!v.valid)
548
+ return { success: false, agentId: input.agentId, error: `Input validation failed: ${v.error}` };
549
+ if (input.status) {
550
+ const vs = validateIdentifier(input.status, 'status');
551
+ if (!vs.valid)
552
+ return { success: false, agentId: input.agentId, error: `Input validation failed: ${vs.error}` };
553
+ }
520
554
  const store = loadAgentStore();
521
555
  const agentId = input.agentId;
522
556
  const agent = store.agents[agentId];