wogiflow 1.0.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.
Files changed (221) hide show
  1. package/.workflow/agents/reviewer.md +81 -0
  2. package/.workflow/agents/security.md +94 -0
  3. package/.workflow/agents/story-writer.md +58 -0
  4. package/.workflow/bridges/base-bridge.js +395 -0
  5. package/.workflow/bridges/claude-bridge.js +434 -0
  6. package/.workflow/bridges/index.js +130 -0
  7. package/.workflow/lib/assumption-detector.js +481 -0
  8. package/.workflow/lib/config-substitution.js +371 -0
  9. package/.workflow/lib/failure-categories.js +478 -0
  10. package/.workflow/state/app-map.md.template +15 -0
  11. package/.workflow/state/architecture.md.template +24 -0
  12. package/.workflow/state/component-index.json.template +5 -0
  13. package/.workflow/state/decisions.md.template +15 -0
  14. package/.workflow/state/feedback-patterns.md.template +9 -0
  15. package/.workflow/state/knowledge-sync.json.template +6 -0
  16. package/.workflow/state/progress.md.template +14 -0
  17. package/.workflow/state/ready.json.template +7 -0
  18. package/.workflow/state/request-log.md.template +14 -0
  19. package/.workflow/state/session-state.json.template +11 -0
  20. package/.workflow/state/stack.md.template +33 -0
  21. package/.workflow/state/testing.md.template +36 -0
  22. package/.workflow/templates/claude-md.hbs +257 -0
  23. package/.workflow/templates/correction-report.md +67 -0
  24. package/.workflow/templates/gemini-md.hbs +52 -0
  25. package/README.md +1802 -0
  26. package/bin/flow +205 -0
  27. package/lib/index.js +33 -0
  28. package/lib/installer.js +467 -0
  29. package/lib/release-channel.js +269 -0
  30. package/lib/skill-registry.js +526 -0
  31. package/lib/upgrader.js +401 -0
  32. package/lib/utils.js +305 -0
  33. package/package.json +64 -0
  34. package/scripts/flow +985 -0
  35. package/scripts/flow-adaptive-learning.js +1259 -0
  36. package/scripts/flow-aggregate.js +488 -0
  37. package/scripts/flow-archive +133 -0
  38. package/scripts/flow-auto-context.js +1015 -0
  39. package/scripts/flow-auto-learn.js +615 -0
  40. package/scripts/flow-bridge.js +223 -0
  41. package/scripts/flow-browser-suggest.js +316 -0
  42. package/scripts/flow-bug.js +247 -0
  43. package/scripts/flow-cascade.js +711 -0
  44. package/scripts/flow-changelog +85 -0
  45. package/scripts/flow-checkpoint.js +483 -0
  46. package/scripts/flow-cli.js +403 -0
  47. package/scripts/flow-code-intelligence.js +760 -0
  48. package/scripts/flow-complexity.js +502 -0
  49. package/scripts/flow-config-set.js +152 -0
  50. package/scripts/flow-constants.js +157 -0
  51. package/scripts/flow-context +152 -0
  52. package/scripts/flow-context-init.js +482 -0
  53. package/scripts/flow-context-monitor.js +384 -0
  54. package/scripts/flow-context-scoring.js +886 -0
  55. package/scripts/flow-correct.js +458 -0
  56. package/scripts/flow-damage-control.js +985 -0
  57. package/scripts/flow-deps +101 -0
  58. package/scripts/flow-diff.js +700 -0
  59. package/scripts/flow-done +151 -0
  60. package/scripts/flow-done.js +489 -0
  61. package/scripts/flow-durable-session.js +1541 -0
  62. package/scripts/flow-entropy-monitor.js +345 -0
  63. package/scripts/flow-export-profile +349 -0
  64. package/scripts/flow-export-scanner.js +1046 -0
  65. package/scripts/flow-figma-confirm.js +400 -0
  66. package/scripts/flow-figma-extract.js +496 -0
  67. package/scripts/flow-figma-generate.js +683 -0
  68. package/scripts/flow-figma-index.js +909 -0
  69. package/scripts/flow-figma-match.js +617 -0
  70. package/scripts/flow-figma-mcp-server.js +518 -0
  71. package/scripts/flow-figma-pipeline.js +414 -0
  72. package/scripts/flow-file-ops.js +301 -0
  73. package/scripts/flow-gate-confidence.js +825 -0
  74. package/scripts/flow-guided-edit.js +659 -0
  75. package/scripts/flow-health +185 -0
  76. package/scripts/flow-health.js +413 -0
  77. package/scripts/flow-hooks.js +556 -0
  78. package/scripts/flow-http-client.js +249 -0
  79. package/scripts/flow-hybrid-detect.js +167 -0
  80. package/scripts/flow-hybrid-interactive.js +591 -0
  81. package/scripts/flow-hybrid-test.js +152 -0
  82. package/scripts/flow-import-profile +439 -0
  83. package/scripts/flow-init +253 -0
  84. package/scripts/flow-instruction-richness.js +827 -0
  85. package/scripts/flow-jira-integration.js +579 -0
  86. package/scripts/flow-knowledge-router.js +522 -0
  87. package/scripts/flow-knowledge-sync.js +589 -0
  88. package/scripts/flow-linear-integration.js +631 -0
  89. package/scripts/flow-links.js +774 -0
  90. package/scripts/flow-log-manager.js +559 -0
  91. package/scripts/flow-loop-enforcer.js +1246 -0
  92. package/scripts/flow-loop-retry-learning.js +630 -0
  93. package/scripts/flow-lsp.js +923 -0
  94. package/scripts/flow-map-index +348 -0
  95. package/scripts/flow-map-sync +201 -0
  96. package/scripts/flow-memory-blocks.js +668 -0
  97. package/scripts/flow-memory-compactor.js +350 -0
  98. package/scripts/flow-memory-db.js +1110 -0
  99. package/scripts/flow-memory-sync.js +484 -0
  100. package/scripts/flow-metrics.js +353 -0
  101. package/scripts/flow-migrate-ids.js +370 -0
  102. package/scripts/flow-model-adapter.js +802 -0
  103. package/scripts/flow-model-router.js +884 -0
  104. package/scripts/flow-models.js +1231 -0
  105. package/scripts/flow-morning.js +517 -0
  106. package/scripts/flow-multi-approach.js +660 -0
  107. package/scripts/flow-new-feature +86 -0
  108. package/scripts/flow-onboard +1042 -0
  109. package/scripts/flow-orchestrate-llm.js +459 -0
  110. package/scripts/flow-orchestrate.js +3592 -0
  111. package/scripts/flow-output.js +123 -0
  112. package/scripts/flow-parallel-detector.js +399 -0
  113. package/scripts/flow-parallel-dispatch.js +987 -0
  114. package/scripts/flow-parallel.js +428 -0
  115. package/scripts/flow-pattern-enforcer.js +600 -0
  116. package/scripts/flow-prd-manager.js +282 -0
  117. package/scripts/flow-progress.js +323 -0
  118. package/scripts/flow-project-analyzer.js +975 -0
  119. package/scripts/flow-prompt-composer.js +487 -0
  120. package/scripts/flow-providers.js +1381 -0
  121. package/scripts/flow-queue.js +308 -0
  122. package/scripts/flow-ready +82 -0
  123. package/scripts/flow-ready.js +189 -0
  124. package/scripts/flow-regression.js +396 -0
  125. package/scripts/flow-response-parser.js +450 -0
  126. package/scripts/flow-resume.js +284 -0
  127. package/scripts/flow-rules-sync.js +439 -0
  128. package/scripts/flow-run-trace.js +718 -0
  129. package/scripts/flow-safety.js +587 -0
  130. package/scripts/flow-search +104 -0
  131. package/scripts/flow-security.js +481 -0
  132. package/scripts/flow-session-end +106 -0
  133. package/scripts/flow-session-end.js +437 -0
  134. package/scripts/flow-session-state.js +671 -0
  135. package/scripts/flow-setup-hooks +216 -0
  136. package/scripts/flow-setup-hooks.js +377 -0
  137. package/scripts/flow-skill-create.js +329 -0
  138. package/scripts/flow-skill-creator.js +572 -0
  139. package/scripts/flow-skill-generator.js +1046 -0
  140. package/scripts/flow-skill-learn.js +880 -0
  141. package/scripts/flow-skill-matcher.js +578 -0
  142. package/scripts/flow-spec-generator.js +820 -0
  143. package/scripts/flow-stack-wizard.js +895 -0
  144. package/scripts/flow-standup +162 -0
  145. package/scripts/flow-start +74 -0
  146. package/scripts/flow-start.js +235 -0
  147. package/scripts/flow-status +110 -0
  148. package/scripts/flow-status.js +301 -0
  149. package/scripts/flow-step-browser.js +83 -0
  150. package/scripts/flow-step-changelog.js +217 -0
  151. package/scripts/flow-step-comments.js +306 -0
  152. package/scripts/flow-step-complexity.js +234 -0
  153. package/scripts/flow-step-coverage.js +218 -0
  154. package/scripts/flow-step-knowledge.js +193 -0
  155. package/scripts/flow-step-pr-tests.js +364 -0
  156. package/scripts/flow-step-regression.js +89 -0
  157. package/scripts/flow-step-review.js +516 -0
  158. package/scripts/flow-step-security.js +162 -0
  159. package/scripts/flow-step-silent-failures.js +290 -0
  160. package/scripts/flow-step-simplifier.js +346 -0
  161. package/scripts/flow-story +105 -0
  162. package/scripts/flow-story.js +500 -0
  163. package/scripts/flow-suspend.js +252 -0
  164. package/scripts/flow-sync-daemon.js +654 -0
  165. package/scripts/flow-task-analyzer.js +606 -0
  166. package/scripts/flow-team-dashboard.js +748 -0
  167. package/scripts/flow-team-sync.js +752 -0
  168. package/scripts/flow-team.js +977 -0
  169. package/scripts/flow-tech-options.js +528 -0
  170. package/scripts/flow-templates.js +812 -0
  171. package/scripts/flow-tiered-learning.js +728 -0
  172. package/scripts/flow-trace +204 -0
  173. package/scripts/flow-transcript-chunking.js +1106 -0
  174. package/scripts/flow-transcript-digest.js +7918 -0
  175. package/scripts/flow-transcript-language.js +465 -0
  176. package/scripts/flow-transcript-parsing.js +1085 -0
  177. package/scripts/flow-transcript-stories.js +2194 -0
  178. package/scripts/flow-update-map +224 -0
  179. package/scripts/flow-utils.js +2242 -0
  180. package/scripts/flow-verification.js +644 -0
  181. package/scripts/flow-verify.js +1177 -0
  182. package/scripts/flow-voice-input.js +638 -0
  183. package/scripts/flow-watch +168 -0
  184. package/scripts/flow-workflow-steps.js +521 -0
  185. package/scripts/flow-workflow.js +1029 -0
  186. package/scripts/flow-worktree.js +489 -0
  187. package/scripts/hooks/adapters/base-adapter.js +102 -0
  188. package/scripts/hooks/adapters/claude-code.js +359 -0
  189. package/scripts/hooks/adapters/index.js +79 -0
  190. package/scripts/hooks/core/component-check.js +341 -0
  191. package/scripts/hooks/core/index.js +35 -0
  192. package/scripts/hooks/core/loop-check.js +241 -0
  193. package/scripts/hooks/core/session-context.js +294 -0
  194. package/scripts/hooks/core/task-gate.js +177 -0
  195. package/scripts/hooks/core/validation.js +230 -0
  196. package/scripts/hooks/entry/claude-code/post-tool-use.js +65 -0
  197. package/scripts/hooks/entry/claude-code/pre-tool-use.js +89 -0
  198. package/scripts/hooks/entry/claude-code/session-end.js +87 -0
  199. package/scripts/hooks/entry/claude-code/session-start.js +46 -0
  200. package/scripts/hooks/entry/claude-code/stop.js +43 -0
  201. package/scripts/postinstall.js +139 -0
  202. package/templates/browser-test-flow.json +56 -0
  203. package/templates/bug-report.md +43 -0
  204. package/templates/component-detail.md +42 -0
  205. package/templates/component.stories.tsx +49 -0
  206. package/templates/context/constraints.md +83 -0
  207. package/templates/context/conventions.md +177 -0
  208. package/templates/context/stack.md +60 -0
  209. package/templates/correction-report.md +90 -0
  210. package/templates/feature-proposal.md +35 -0
  211. package/templates/hybrid/_base.md +254 -0
  212. package/templates/hybrid/_patterns.md +45 -0
  213. package/templates/hybrid/create-component.md +127 -0
  214. package/templates/hybrid/create-file.md +56 -0
  215. package/templates/hybrid/create-hook.md +145 -0
  216. package/templates/hybrid/create-service.md +70 -0
  217. package/templates/hybrid/fix-bug.md +33 -0
  218. package/templates/hybrid/modify-file.md +55 -0
  219. package/templates/story.md +68 -0
  220. package/templates/task.json +56 -0
  221. package/templates/trace.md +69 -0
@@ -0,0 +1,249 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - HTTP Client
5
+ *
6
+ * Shared HTTP client with consistent error handling, timeouts, and retries.
7
+ * Extracted from flow-team.js, flow-jira-integration.js, flow-linear-integration.js.
8
+ *
9
+ * Usage:
10
+ * const { HttpClient, fetchJson, postJson } = require('./flow-http-client');
11
+ * const client = new HttpClient('https://api.example.com', { timeout: 30000 });
12
+ * const data = await client.get('/endpoint');
13
+ */
14
+
15
+ const https = require('https');
16
+ const http = require('http');
17
+ const { TIMEOUTS, LIMITS, BACKOFF } = require('./flow-constants');
18
+
19
+ /**
20
+ * HTTP Client class with built-in error handling and retries
21
+ */
22
+ class HttpClient {
23
+ constructor(baseUrl, options = {}) {
24
+ this.baseUrl = baseUrl;
25
+ this.defaultHeaders = options.headers || {};
26
+ this.timeout = options.timeout || TIMEOUTS.HTTP_DEFAULT;
27
+ this.maxRetries = options.maxRetries || LIMITS.HTTP_MAX_RETRIES;
28
+ }
29
+
30
+ /**
31
+ * Make an HTTP request
32
+ * @param {string} method - HTTP method
33
+ * @param {string} path - URL path
34
+ * @param {object|null} body - Request body (will be JSON stringified)
35
+ * @param {object} options - Additional options
36
+ * @returns {Promise<{status: number, data: any, headers: object}>}
37
+ */
38
+ async request(method, path, body = null, options = {}) {
39
+ const url = new URL(path, this.baseUrl);
40
+ const isHttps = url.protocol === 'https:';
41
+ const lib = isHttps ? https : http;
42
+
43
+ const headers = {
44
+ ...this.defaultHeaders,
45
+ ...options.headers,
46
+ };
47
+
48
+ if (body && !headers['Content-Type']) {
49
+ headers['Content-Type'] = 'application/json';
50
+ }
51
+
52
+ const requestOptions = {
53
+ method,
54
+ hostname: url.hostname,
55
+ port: url.port || (isHttps ? 443 : 80),
56
+ path: url.pathname + url.search,
57
+ headers,
58
+ timeout: options.timeout || this.timeout,
59
+ };
60
+
61
+ return this._executeWithRetry(lib, requestOptions, body, options.retries || 0);
62
+ }
63
+
64
+ /**
65
+ * Execute request with retry logic
66
+ */
67
+ async _executeWithRetry(lib, options, body, attempt) {
68
+ try {
69
+ return await this._execute(lib, options, body);
70
+ } catch (err) {
71
+ if (attempt < this.maxRetries && this._isRetryable(err)) {
72
+ const delay = this._calculateBackoff(attempt);
73
+ await this._sleep(delay);
74
+ return this._executeWithRetry(lib, options, body, attempt + 1);
75
+ }
76
+ throw err;
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Execute a single HTTP request
82
+ */
83
+ _execute(lib, options, body) {
84
+ return new Promise((resolve, reject) => {
85
+ const req = lib.request(options, (res) => {
86
+ let data = '';
87
+ res.on('data', chunk => data += chunk);
88
+ res.on('end', () => {
89
+ let parsed = data;
90
+ try {
91
+ if (data && res.headers['content-type']?.includes('application/json')) {
92
+ parsed = JSON.parse(data);
93
+ }
94
+ } catch (err) {
95
+ // Keep as string if not valid JSON, but log for debugging
96
+ if (process.env.DEBUG) {
97
+ console.warn(`[HttpClient] Failed to parse JSON response: ${err.message}`);
98
+ }
99
+ }
100
+
101
+ resolve({
102
+ status: res.statusCode,
103
+ data: parsed,
104
+ headers: res.headers,
105
+ });
106
+ });
107
+ });
108
+
109
+ req.on('error', reject);
110
+ req.on('timeout', () => {
111
+ req.destroy();
112
+ reject(new Error('Request timeout'));
113
+ });
114
+
115
+ if (body) {
116
+ if (Buffer.isBuffer(body)) {
117
+ req.write(body);
118
+ } else if (typeof body === 'string') {
119
+ req.write(body);
120
+ } else {
121
+ req.write(JSON.stringify(body));
122
+ }
123
+ }
124
+ req.end();
125
+ });
126
+ }
127
+
128
+ /**
129
+ * Check if error is retryable
130
+ */
131
+ _isRetryable(err) {
132
+ if (err.code === 'ECONNRESET' || err.code === 'ETIMEDOUT') return true;
133
+ if (err.message === 'Request timeout') return true;
134
+ return false;
135
+ }
136
+
137
+ /**
138
+ * Calculate exponential backoff delay
139
+ */
140
+ _calculateBackoff(attempt) {
141
+ const base = BACKOFF.BASE_DELAY * Math.pow(BACKOFF.MULTIPLIER, attempt);
142
+ const jitter = base * BACKOFF.JITTER * Math.random();
143
+ return Math.min(base + jitter, BACKOFF.MAX_DELAY);
144
+ }
145
+
146
+ /**
147
+ * Sleep helper
148
+ */
149
+ _sleep(ms) {
150
+ return new Promise(resolve => setTimeout(resolve, ms));
151
+ }
152
+
153
+ // Convenience methods
154
+ get(path, options = {}) {
155
+ return this.request('GET', path, null, options);
156
+ }
157
+
158
+ post(path, body, options = {}) {
159
+ return this.request('POST', path, body, options);
160
+ }
161
+
162
+ put(path, body, options = {}) {
163
+ return this.request('PUT', path, body, options);
164
+ }
165
+
166
+ patch(path, body, options = {}) {
167
+ return this.request('PATCH', path, body, options);
168
+ }
169
+
170
+ delete(path, options = {}) {
171
+ return this.request('DELETE', path, null, options);
172
+ }
173
+
174
+ /**
175
+ * Post multipart form data (for file uploads)
176
+ * @param {string} path - URL path
177
+ * @param {Array<{name: string, value: string|Buffer, filename?: string, contentType?: string}>} parts - Form parts
178
+ * @param {object} options - Additional options
179
+ * @returns {Promise<{status: number, data: any, headers: object}>}
180
+ */
181
+ async postMultipart(path, parts, options = {}) {
182
+ const boundary = '----HttpClientBoundary' + Math.random().toString(36).substring(2);
183
+ const chunks = [];
184
+
185
+ for (const part of parts) {
186
+ chunks.push(Buffer.from(`--${boundary}\r\n`));
187
+
188
+ if (part.filename) {
189
+ // File part
190
+ chunks.push(Buffer.from(
191
+ `Content-Disposition: form-data; name="${part.name}"; filename="${part.filename}"\r\n`
192
+ ));
193
+ chunks.push(Buffer.from(
194
+ `Content-Type: ${part.contentType || 'application/octet-stream'}\r\n\r\n`
195
+ ));
196
+ } else {
197
+ // Regular field
198
+ chunks.push(Buffer.from(
199
+ `Content-Disposition: form-data; name="${part.name}"\r\n\r\n`
200
+ ));
201
+ }
202
+
203
+ chunks.push(Buffer.isBuffer(part.value) ? part.value : Buffer.from(String(part.value)));
204
+ chunks.push(Buffer.from('\r\n'));
205
+ }
206
+
207
+ chunks.push(Buffer.from(`--${boundary}--\r\n`));
208
+ const body = Buffer.concat(chunks);
209
+
210
+ return this.request('POST', path, body, {
211
+ ...options,
212
+ headers: {
213
+ ...options.headers,
214
+ 'Content-Type': `multipart/form-data; boundary=${boundary}`,
215
+ 'Content-Length': body.length
216
+ }
217
+ });
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Simple fetch JSON helper (for one-off requests)
223
+ */
224
+ async function fetchJson(url, options = {}) {
225
+ const client = new HttpClient(url, { timeout: options.timeout || TIMEOUTS.HTTP_DEFAULT });
226
+ const parsedUrl = new URL(url);
227
+ const response = await client.get(parsedUrl.pathname + parsedUrl.search, {
228
+ headers: options.headers,
229
+ });
230
+ return response.data;
231
+ }
232
+
233
+ /**
234
+ * Simple post JSON helper (for one-off requests)
235
+ */
236
+ async function postJson(url, body, options = {}) {
237
+ const client = new HttpClient(url, { timeout: options.timeout || TIMEOUTS.HTTP_DEFAULT });
238
+ const parsedUrl = new URL(url);
239
+ const response = await client.post(parsedUrl.pathname + parsedUrl.search, body, {
240
+ headers: options.headers,
241
+ });
242
+ return response.data;
243
+ }
244
+
245
+ module.exports = {
246
+ HttpClient,
247
+ fetchJson,
248
+ postJson,
249
+ };
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Local LLM Provider Detection
5
+ *
6
+ * Detects Ollama and LM Studio, lists available models.
7
+ * Usage:
8
+ * flow-hybrid-detect providers # List available providers
9
+ * flow-hybrid-detect models # List models for all providers
10
+ * flow-hybrid-detect test <url> # Test a specific endpoint
11
+ */
12
+
13
+ const http = require('http');
14
+ const https = require('https');
15
+
16
+ const PROVIDERS = {
17
+ ollama: {
18
+ name: 'Ollama',
19
+ defaultEndpoint: 'http://localhost:11434',
20
+ checkPath: '/api/tags',
21
+ modelsPath: '/api/tags',
22
+ parseModels: (data) => data.models?.map(m => ({
23
+ id: m.name,
24
+ name: m.name,
25
+ size: m.size,
26
+ modified: m.modified_at
27
+ })) || []
28
+ },
29
+ lmstudio: {
30
+ name: 'LM Studio',
31
+ defaultEndpoint: 'http://localhost:1234',
32
+ checkPath: '/v1/models',
33
+ modelsPath: '/v1/models',
34
+ parseModels: (data) => data.data?.map(m => ({
35
+ id: m.id,
36
+ name: m.id,
37
+ owned_by: m.owned_by
38
+ })) || []
39
+ }
40
+ };
41
+
42
+ async function fetchJSON(url, timeout = 3000) {
43
+ return new Promise((resolve, reject) => {
44
+ const client = url.startsWith('https') ? https : http;
45
+ const req = client.get(url, { timeout }, (res) => {
46
+ let data = '';
47
+ res.on('data', chunk => data += chunk);
48
+ res.on('end', () => {
49
+ try {
50
+ resolve(JSON.parse(data));
51
+ } catch (err) {
52
+ reject(new Error('Invalid JSON response'));
53
+ }
54
+ });
55
+ });
56
+ req.on('error', reject);
57
+ req.on('timeout', () => {
58
+ req.destroy();
59
+ reject(new Error('Timeout'));
60
+ });
61
+ });
62
+ }
63
+
64
+ async function checkProvider(providerId) {
65
+ const provider = PROVIDERS[providerId];
66
+ if (!provider) return null;
67
+
68
+ try {
69
+ const url = `${provider.defaultEndpoint}${provider.checkPath}`;
70
+ const data = await fetchJSON(url);
71
+ const models = provider.parseModels(data);
72
+
73
+ return {
74
+ id: providerId,
75
+ name: provider.name,
76
+ endpoint: provider.defaultEndpoint,
77
+ available: true,
78
+ models
79
+ };
80
+ } catch (err) {
81
+ return {
82
+ id: providerId,
83
+ name: provider.name,
84
+ endpoint: provider.defaultEndpoint,
85
+ available: false,
86
+ error: err.message
87
+ };
88
+ }
89
+ }
90
+
91
+ async function detectAll() {
92
+ const results = await Promise.all(
93
+ Object.keys(PROVIDERS).map(checkProvider)
94
+ );
95
+ return results;
96
+ }
97
+
98
+ async function testConnection(endpoint, model) {
99
+ const isOllama = endpoint.includes('11434');
100
+
101
+ try {
102
+ if (isOllama) {
103
+ const response = await fetchJSON(`${endpoint}/api/tags`, 5000);
104
+ return { success: true, message: 'Connection successful', models: response.models?.length || 0 };
105
+ } else {
106
+ const response = await fetchJSON(`${endpoint}/v1/models`);
107
+ return { success: true, message: 'Connection successful', models: response.data?.length || 0 };
108
+ }
109
+ } catch (err) {
110
+ return { success: false, error: err.message };
111
+ }
112
+ }
113
+
114
+ // CLI
115
+ async function main() {
116
+ const [,, command, ...args] = process.argv;
117
+
118
+ switch (command) {
119
+ case 'providers': {
120
+ const providers = await detectAll();
121
+ console.log(JSON.stringify(providers, null, 2));
122
+ break;
123
+ }
124
+
125
+ case 'models': {
126
+ const providers = await detectAll();
127
+ const available = providers.filter(p => p.available);
128
+
129
+ if (available.length === 0) {
130
+ console.log(JSON.stringify({ error: 'No providers available' }));
131
+ process.exit(1);
132
+ }
133
+
134
+ const allModels = available.flatMap(p =>
135
+ p.models.map(m => ({ ...m, provider: p.id, endpoint: p.endpoint }))
136
+ );
137
+ console.log(JSON.stringify(allModels, null, 2));
138
+ break;
139
+ }
140
+
141
+ case 'test': {
142
+ const [endpoint, model] = args;
143
+ if (!endpoint) {
144
+ console.error('Usage: flow-hybrid-detect test <endpoint> [model]');
145
+ process.exit(1);
146
+ }
147
+ const result = await testConnection(endpoint, model);
148
+ console.log(JSON.stringify(result, null, 2));
149
+ break;
150
+ }
151
+
152
+ default:
153
+ console.log(`
154
+ Wogi Flow - Local LLM Detection
155
+
156
+ Commands:
157
+ providers List available providers (Ollama, LM Studio)
158
+ models List all models from available providers
159
+ test <url> Test connection to endpoint
160
+ `);
161
+ }
162
+ }
163
+
164
+ main().catch(e => {
165
+ console.error(JSON.stringify({ error: err.message }));
166
+ process.exit(1);
167
+ });