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.
- package/.workflow/agents/reviewer.md +81 -0
- package/.workflow/agents/security.md +94 -0
- package/.workflow/agents/story-writer.md +58 -0
- package/.workflow/bridges/base-bridge.js +395 -0
- package/.workflow/bridges/claude-bridge.js +434 -0
- package/.workflow/bridges/index.js +130 -0
- package/.workflow/lib/assumption-detector.js +481 -0
- package/.workflow/lib/config-substitution.js +371 -0
- package/.workflow/lib/failure-categories.js +478 -0
- package/.workflow/state/app-map.md.template +15 -0
- package/.workflow/state/architecture.md.template +24 -0
- package/.workflow/state/component-index.json.template +5 -0
- package/.workflow/state/decisions.md.template +15 -0
- package/.workflow/state/feedback-patterns.md.template +9 -0
- package/.workflow/state/knowledge-sync.json.template +6 -0
- package/.workflow/state/progress.md.template +14 -0
- package/.workflow/state/ready.json.template +7 -0
- package/.workflow/state/request-log.md.template +14 -0
- package/.workflow/state/session-state.json.template +11 -0
- package/.workflow/state/stack.md.template +33 -0
- package/.workflow/state/testing.md.template +36 -0
- package/.workflow/templates/claude-md.hbs +257 -0
- package/.workflow/templates/correction-report.md +67 -0
- package/.workflow/templates/gemini-md.hbs +52 -0
- package/README.md +1802 -0
- package/bin/flow +205 -0
- package/lib/index.js +33 -0
- package/lib/installer.js +467 -0
- package/lib/release-channel.js +269 -0
- package/lib/skill-registry.js +526 -0
- package/lib/upgrader.js +401 -0
- package/lib/utils.js +305 -0
- package/package.json +64 -0
- package/scripts/flow +985 -0
- package/scripts/flow-adaptive-learning.js +1259 -0
- package/scripts/flow-aggregate.js +488 -0
- package/scripts/flow-archive +133 -0
- package/scripts/flow-auto-context.js +1015 -0
- package/scripts/flow-auto-learn.js +615 -0
- package/scripts/flow-bridge.js +223 -0
- package/scripts/flow-browser-suggest.js +316 -0
- package/scripts/flow-bug.js +247 -0
- package/scripts/flow-cascade.js +711 -0
- package/scripts/flow-changelog +85 -0
- package/scripts/flow-checkpoint.js +483 -0
- package/scripts/flow-cli.js +403 -0
- package/scripts/flow-code-intelligence.js +760 -0
- package/scripts/flow-complexity.js +502 -0
- package/scripts/flow-config-set.js +152 -0
- package/scripts/flow-constants.js +157 -0
- package/scripts/flow-context +152 -0
- package/scripts/flow-context-init.js +482 -0
- package/scripts/flow-context-monitor.js +384 -0
- package/scripts/flow-context-scoring.js +886 -0
- package/scripts/flow-correct.js +458 -0
- package/scripts/flow-damage-control.js +985 -0
- package/scripts/flow-deps +101 -0
- package/scripts/flow-diff.js +700 -0
- package/scripts/flow-done +151 -0
- package/scripts/flow-done.js +489 -0
- package/scripts/flow-durable-session.js +1541 -0
- package/scripts/flow-entropy-monitor.js +345 -0
- package/scripts/flow-export-profile +349 -0
- package/scripts/flow-export-scanner.js +1046 -0
- package/scripts/flow-figma-confirm.js +400 -0
- package/scripts/flow-figma-extract.js +496 -0
- package/scripts/flow-figma-generate.js +683 -0
- package/scripts/flow-figma-index.js +909 -0
- package/scripts/flow-figma-match.js +617 -0
- package/scripts/flow-figma-mcp-server.js +518 -0
- package/scripts/flow-figma-pipeline.js +414 -0
- package/scripts/flow-file-ops.js +301 -0
- package/scripts/flow-gate-confidence.js +825 -0
- package/scripts/flow-guided-edit.js +659 -0
- package/scripts/flow-health +185 -0
- package/scripts/flow-health.js +413 -0
- package/scripts/flow-hooks.js +556 -0
- package/scripts/flow-http-client.js +249 -0
- package/scripts/flow-hybrid-detect.js +167 -0
- package/scripts/flow-hybrid-interactive.js +591 -0
- package/scripts/flow-hybrid-test.js +152 -0
- package/scripts/flow-import-profile +439 -0
- package/scripts/flow-init +253 -0
- package/scripts/flow-instruction-richness.js +827 -0
- package/scripts/flow-jira-integration.js +579 -0
- package/scripts/flow-knowledge-router.js +522 -0
- package/scripts/flow-knowledge-sync.js +589 -0
- package/scripts/flow-linear-integration.js +631 -0
- package/scripts/flow-links.js +774 -0
- package/scripts/flow-log-manager.js +559 -0
- package/scripts/flow-loop-enforcer.js +1246 -0
- package/scripts/flow-loop-retry-learning.js +630 -0
- package/scripts/flow-lsp.js +923 -0
- package/scripts/flow-map-index +348 -0
- package/scripts/flow-map-sync +201 -0
- package/scripts/flow-memory-blocks.js +668 -0
- package/scripts/flow-memory-compactor.js +350 -0
- package/scripts/flow-memory-db.js +1110 -0
- package/scripts/flow-memory-sync.js +484 -0
- package/scripts/flow-metrics.js +353 -0
- package/scripts/flow-migrate-ids.js +370 -0
- package/scripts/flow-model-adapter.js +802 -0
- package/scripts/flow-model-router.js +884 -0
- package/scripts/flow-models.js +1231 -0
- package/scripts/flow-morning.js +517 -0
- package/scripts/flow-multi-approach.js +660 -0
- package/scripts/flow-new-feature +86 -0
- package/scripts/flow-onboard +1042 -0
- package/scripts/flow-orchestrate-llm.js +459 -0
- package/scripts/flow-orchestrate.js +3592 -0
- package/scripts/flow-output.js +123 -0
- package/scripts/flow-parallel-detector.js +399 -0
- package/scripts/flow-parallel-dispatch.js +987 -0
- package/scripts/flow-parallel.js +428 -0
- package/scripts/flow-pattern-enforcer.js +600 -0
- package/scripts/flow-prd-manager.js +282 -0
- package/scripts/flow-progress.js +323 -0
- package/scripts/flow-project-analyzer.js +975 -0
- package/scripts/flow-prompt-composer.js +487 -0
- package/scripts/flow-providers.js +1381 -0
- package/scripts/flow-queue.js +308 -0
- package/scripts/flow-ready +82 -0
- package/scripts/flow-ready.js +189 -0
- package/scripts/flow-regression.js +396 -0
- package/scripts/flow-response-parser.js +450 -0
- package/scripts/flow-resume.js +284 -0
- package/scripts/flow-rules-sync.js +439 -0
- package/scripts/flow-run-trace.js +718 -0
- package/scripts/flow-safety.js +587 -0
- package/scripts/flow-search +104 -0
- package/scripts/flow-security.js +481 -0
- package/scripts/flow-session-end +106 -0
- package/scripts/flow-session-end.js +437 -0
- package/scripts/flow-session-state.js +671 -0
- package/scripts/flow-setup-hooks +216 -0
- package/scripts/flow-setup-hooks.js +377 -0
- package/scripts/flow-skill-create.js +329 -0
- package/scripts/flow-skill-creator.js +572 -0
- package/scripts/flow-skill-generator.js +1046 -0
- package/scripts/flow-skill-learn.js +880 -0
- package/scripts/flow-skill-matcher.js +578 -0
- package/scripts/flow-spec-generator.js +820 -0
- package/scripts/flow-stack-wizard.js +895 -0
- package/scripts/flow-standup +162 -0
- package/scripts/flow-start +74 -0
- package/scripts/flow-start.js +235 -0
- package/scripts/flow-status +110 -0
- package/scripts/flow-status.js +301 -0
- package/scripts/flow-step-browser.js +83 -0
- package/scripts/flow-step-changelog.js +217 -0
- package/scripts/flow-step-comments.js +306 -0
- package/scripts/flow-step-complexity.js +234 -0
- package/scripts/flow-step-coverage.js +218 -0
- package/scripts/flow-step-knowledge.js +193 -0
- package/scripts/flow-step-pr-tests.js +364 -0
- package/scripts/flow-step-regression.js +89 -0
- package/scripts/flow-step-review.js +516 -0
- package/scripts/flow-step-security.js +162 -0
- package/scripts/flow-step-silent-failures.js +290 -0
- package/scripts/flow-step-simplifier.js +346 -0
- package/scripts/flow-story +105 -0
- package/scripts/flow-story.js +500 -0
- package/scripts/flow-suspend.js +252 -0
- package/scripts/flow-sync-daemon.js +654 -0
- package/scripts/flow-task-analyzer.js +606 -0
- package/scripts/flow-team-dashboard.js +748 -0
- package/scripts/flow-team-sync.js +752 -0
- package/scripts/flow-team.js +977 -0
- package/scripts/flow-tech-options.js +528 -0
- package/scripts/flow-templates.js +812 -0
- package/scripts/flow-tiered-learning.js +728 -0
- package/scripts/flow-trace +204 -0
- package/scripts/flow-transcript-chunking.js +1106 -0
- package/scripts/flow-transcript-digest.js +7918 -0
- package/scripts/flow-transcript-language.js +465 -0
- package/scripts/flow-transcript-parsing.js +1085 -0
- package/scripts/flow-transcript-stories.js +2194 -0
- package/scripts/flow-update-map +224 -0
- package/scripts/flow-utils.js +2242 -0
- package/scripts/flow-verification.js +644 -0
- package/scripts/flow-verify.js +1177 -0
- package/scripts/flow-voice-input.js +638 -0
- package/scripts/flow-watch +168 -0
- package/scripts/flow-workflow-steps.js +521 -0
- package/scripts/flow-workflow.js +1029 -0
- package/scripts/flow-worktree.js +489 -0
- package/scripts/hooks/adapters/base-adapter.js +102 -0
- package/scripts/hooks/adapters/claude-code.js +359 -0
- package/scripts/hooks/adapters/index.js +79 -0
- package/scripts/hooks/core/component-check.js +341 -0
- package/scripts/hooks/core/index.js +35 -0
- package/scripts/hooks/core/loop-check.js +241 -0
- package/scripts/hooks/core/session-context.js +294 -0
- package/scripts/hooks/core/task-gate.js +177 -0
- package/scripts/hooks/core/validation.js +230 -0
- package/scripts/hooks/entry/claude-code/post-tool-use.js +65 -0
- package/scripts/hooks/entry/claude-code/pre-tool-use.js +89 -0
- package/scripts/hooks/entry/claude-code/session-end.js +87 -0
- package/scripts/hooks/entry/claude-code/session-start.js +46 -0
- package/scripts/hooks/entry/claude-code/stop.js +43 -0
- package/scripts/postinstall.js +139 -0
- package/templates/browser-test-flow.json +56 -0
- package/templates/bug-report.md +43 -0
- package/templates/component-detail.md +42 -0
- package/templates/component.stories.tsx +49 -0
- package/templates/context/constraints.md +83 -0
- package/templates/context/conventions.md +177 -0
- package/templates/context/stack.md +60 -0
- package/templates/correction-report.md +90 -0
- package/templates/feature-proposal.md +35 -0
- package/templates/hybrid/_base.md +254 -0
- package/templates/hybrid/_patterns.md +45 -0
- package/templates/hybrid/create-component.md +127 -0
- package/templates/hybrid/create-file.md +56 -0
- package/templates/hybrid/create-hook.md +145 -0
- package/templates/hybrid/create-service.md +70 -0
- package/templates/hybrid/fix-bug.md +33 -0
- package/templates/hybrid/modify-file.md +55 -0
- package/templates/story.md +68 -0
- package/templates/task.json +56 -0
- 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
|
+
});
|