zero-doc 1.0.14 ā 1.0.16
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/dist/ai-analyzer.js +20 -4
- package/dist/cli.js +5 -0
- package/package.json +1 -1
- package/viewer/src/components/CodeSnippet.tsx +160 -7
package/dist/ai-analyzer.js
CHANGED
|
@@ -147,9 +147,9 @@ class AIAnalyzer {
|
|
|
147
147
|
if (!this.apiUrl) {
|
|
148
148
|
throw new Error('Proxy API URL not configured');
|
|
149
149
|
}
|
|
150
|
-
const
|
|
151
|
-
if (!
|
|
152
|
-
throw new Error(`Invalid API URL: "${
|
|
150
|
+
const baseUrl = this.apiUrl.trim();
|
|
151
|
+
if (!baseUrl.startsWith('http://') && !baseUrl.startsWith('https://')) {
|
|
152
|
+
throw new Error(`Invalid API URL: "${baseUrl}". URL must start with http:// or https://`);
|
|
153
153
|
}
|
|
154
154
|
try {
|
|
155
155
|
const headers = {
|
|
@@ -159,7 +159,23 @@ class AIAnalyzer {
|
|
|
159
159
|
if (this.token) {
|
|
160
160
|
headers['Authorization'] = `Bearer ${this.token}`;
|
|
161
161
|
}
|
|
162
|
-
|
|
162
|
+
// First, get the actual URL to use for the prompt request
|
|
163
|
+
const urlEndpoint = `${baseUrl}/url`;
|
|
164
|
+
const urlResponse = await fetch(urlEndpoint, {
|
|
165
|
+
method: 'GET',
|
|
166
|
+
headers,
|
|
167
|
+
});
|
|
168
|
+
if (!urlResponse.ok) {
|
|
169
|
+
const errorText = await urlResponse.text().catch(() => 'No error details');
|
|
170
|
+
throw new Error(`Failed to get API URL (${urlResponse.status}): ${urlResponse.statusText}. ${errorText}`);
|
|
171
|
+
}
|
|
172
|
+
const urlData = await urlResponse.json();
|
|
173
|
+
const actualUrl = urlData.url || urlData.endpoint || urlData.apiUrl;
|
|
174
|
+
if (!actualUrl || (typeof actualUrl !== 'string')) {
|
|
175
|
+
throw new Error('Invalid response from /url endpoint: missing or invalid URL');
|
|
176
|
+
}
|
|
177
|
+
// Now make the actual prompt request to the URL we got
|
|
178
|
+
const response = await fetch(actualUrl, {
|
|
163
179
|
method: 'POST',
|
|
164
180
|
headers,
|
|
165
181
|
body: JSON.stringify({ prompt }),
|
package/dist/cli.js
CHANGED
|
@@ -676,6 +676,11 @@ commander_1.program
|
|
|
676
676
|
}
|
|
677
677
|
});
|
|
678
678
|
server.listen(CALLBACK_PORT, () => {
|
|
679
|
+
// Display login link
|
|
680
|
+
const reset = '\x1b[0m';
|
|
681
|
+
const blue = '\x1b[34m';
|
|
682
|
+
const underline = '\x1b[4m';
|
|
683
|
+
console.log(`\nš Login URL: ${blue}${underline}${LOGIN_URL}${reset}\n`);
|
|
679
684
|
// Open browser silently
|
|
680
685
|
let openCommand;
|
|
681
686
|
if (process.platform === 'win32') {
|
package/package.json
CHANGED
|
@@ -8,6 +8,9 @@ interface CodeSnippetProps {
|
|
|
8
8
|
|
|
9
9
|
const CodeSnippet: React.FC<CodeSnippetProps> = ({ endpoint, baseUrl = 'https://api.example.com' }) => {
|
|
10
10
|
const [showToast, setShowToast] = useState(false);
|
|
11
|
+
const [isExecuting, setIsExecuting] = useState(false);
|
|
12
|
+
const [response, setResponse] = useState<{ status: number; data: any; headers: Record<string, string> } | null>(null);
|
|
13
|
+
const [showResponse, setShowResponse] = useState(false);
|
|
11
14
|
|
|
12
15
|
const pathParams = endpoint.parameters.filter(p => p.in === 'path');
|
|
13
16
|
const queryParams = endpoint.parameters.filter(p => p.in === 'query');
|
|
@@ -69,6 +72,89 @@ const CodeSnippet: React.FC<CodeSnippetProps> = ({ endpoint, baseUrl = 'https://
|
|
|
69
72
|
}
|
|
70
73
|
};
|
|
71
74
|
|
|
75
|
+
const executeRequest = async () => {
|
|
76
|
+
setIsExecuting(true);
|
|
77
|
+
setResponse(null);
|
|
78
|
+
setShowResponse(true);
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
let actualUrl = endpoint.path;
|
|
82
|
+
pathParams.forEach(param => {
|
|
83
|
+
actualUrl = actualUrl.replace(`:${param.name}`, '1');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const actualQueryString = queryParams.length > 0
|
|
87
|
+
? '?' + queryParams.map(p => `${p.name}=${p.inferredType === 'number' ? '1' : 'value'}`).join('&')
|
|
88
|
+
: '';
|
|
89
|
+
|
|
90
|
+
const cleanBaseUrl = baseUrl.replace(/\/$/, '');
|
|
91
|
+
const cleanPath = actualUrl.startsWith('/') ? actualUrl : `/${actualUrl}`;
|
|
92
|
+
const requestUrl = `${cleanBaseUrl}${cleanPath}${actualQueryString}`;
|
|
93
|
+
|
|
94
|
+
const headers: Record<string, string> = {};
|
|
95
|
+
|
|
96
|
+
if (needsAuth) {
|
|
97
|
+
if (endpoint.authentication?.type === 'bearer') {
|
|
98
|
+
headers['Authorization'] = 'Bearer YOUR_TOKEN_HERE';
|
|
99
|
+
} else if (endpoint.authentication?.type === 'api-key') {
|
|
100
|
+
headers['X-API-Key'] = 'YOUR_API_KEY_HERE';
|
|
101
|
+
} else {
|
|
102
|
+
headers['Authorization'] = 'YOUR_AUTH_TOKEN_HERE';
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
headerParams.forEach(header => {
|
|
107
|
+
headers[header.name] = header.required ? 'REQUIRED_VALUE' : 'OPTIONAL_VALUE';
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
let body: string | undefined;
|
|
111
|
+
if (bodyParams.length > 0) {
|
|
112
|
+
const bodyObj = bodyParams.reduce((acc, param) => {
|
|
113
|
+
const value = param.inferredType === 'number' ? 0 :
|
|
114
|
+
param.inferredType === 'boolean' ? true :
|
|
115
|
+
'value';
|
|
116
|
+
acc[param.name] = value;
|
|
117
|
+
return acc;
|
|
118
|
+
}, {} as Record<string, any>);
|
|
119
|
+
body = JSON.stringify(bodyObj);
|
|
120
|
+
headers['Content-Type'] = 'application/json';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const fetchResponse = await fetch(requestUrl, {
|
|
124
|
+
method: endpoint.method,
|
|
125
|
+
headers,
|
|
126
|
+
body: body,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const responseHeaders: Record<string, string> = {};
|
|
130
|
+
fetchResponse.headers.forEach((value, key) => {
|
|
131
|
+
responseHeaders[key] = value;
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
let responseData: any;
|
|
135
|
+
const contentType = fetchResponse.headers.get('content-type');
|
|
136
|
+
if (contentType && contentType.includes('application/json')) {
|
|
137
|
+
responseData = await fetchResponse.json();
|
|
138
|
+
} else {
|
|
139
|
+
responseData = await fetchResponse.text();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
setResponse({
|
|
143
|
+
status: fetchResponse.status,
|
|
144
|
+
data: responseData,
|
|
145
|
+
headers: responseHeaders,
|
|
146
|
+
});
|
|
147
|
+
} catch (error: any) {
|
|
148
|
+
setResponse({
|
|
149
|
+
status: 0,
|
|
150
|
+
data: { error: error.message || 'Request failed' },
|
|
151
|
+
headers: {},
|
|
152
|
+
});
|
|
153
|
+
} finally {
|
|
154
|
+
setIsExecuting(false);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
72
158
|
return (
|
|
73
159
|
<>
|
|
74
160
|
<div className="bg-gray-900 rounded-lg border border-gray-700 overflow-hidden shadow-lg">
|
|
@@ -81,13 +167,39 @@ const CodeSnippet: React.FC<CodeSnippetProps> = ({ endpoint, baseUrl = 'https://
|
|
|
81
167
|
</div>
|
|
82
168
|
<span className="text-xs font-medium text-gray-400 uppercase tracking-wider">curl</span>
|
|
83
169
|
</div>
|
|
84
|
-
<
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
170
|
+
<div className="flex items-center gap-2">
|
|
171
|
+
<button
|
|
172
|
+
onClick={executeRequest}
|
|
173
|
+
disabled={isExecuting}
|
|
174
|
+
className="px-3 py-1.5 text-xs text-white bg-blue-600 hover:bg-blue-700 disabled:bg-gray-600 disabled:cursor-not-allowed transition-colors rounded border border-blue-500 hover:border-blue-600 flex items-center gap-1.5"
|
|
175
|
+
title="Try it out"
|
|
176
|
+
>
|
|
177
|
+
{isExecuting ? (
|
|
178
|
+
<>
|
|
179
|
+
<svg className="animate-spin h-3 w-3" fill="none" viewBox="0 0 24 24">
|
|
180
|
+
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
|
181
|
+
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
182
|
+
</svg>
|
|
183
|
+
Executing...
|
|
184
|
+
</>
|
|
185
|
+
) : (
|
|
186
|
+
<>
|
|
187
|
+
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
188
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
|
|
189
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
190
|
+
</svg>
|
|
191
|
+
Try it out
|
|
192
|
+
</>
|
|
193
|
+
)}
|
|
194
|
+
</button>
|
|
195
|
+
<button
|
|
196
|
+
onClick={() => copyToClipboard(curlCommand)}
|
|
197
|
+
className="px-3 py-1.5 text-xs text-gray-400 hover:text-white transition-colors rounded hover:bg-gray-700/50 border border-gray-700 hover:border-gray-600"
|
|
198
|
+
title="Copy to clipboard"
|
|
199
|
+
>
|
|
200
|
+
š Copy
|
|
201
|
+
</button>
|
|
202
|
+
</div>
|
|
91
203
|
</div>
|
|
92
204
|
<div className="p-5 overflow-x-auto bg-gray-950">
|
|
93
205
|
<pre className="text-sm text-gray-100 font-mono leading-relaxed whitespace-pre-wrap">
|
|
@@ -166,6 +278,47 @@ const CodeSnippet: React.FC<CodeSnippetProps> = ({ endpoint, baseUrl = 'https://
|
|
|
166
278
|
</div>
|
|
167
279
|
</div>
|
|
168
280
|
|
|
281
|
+
{/* Response Section */}
|
|
282
|
+
{showResponse && response && (
|
|
283
|
+
<div className="mt-4 bg-gray-900 rounded-lg border border-gray-700 overflow-hidden shadow-lg">
|
|
284
|
+
<div className="flex items-center justify-between px-4 py-3 border-b border-gray-700 bg-gray-800/50">
|
|
285
|
+
<div className="flex items-center gap-2">
|
|
286
|
+
<div className="flex gap-1.5">
|
|
287
|
+
<div className="w-3 h-3 rounded-full bg-red-500"></div>
|
|
288
|
+
<div className="w-3 h-3 rounded-full bg-yellow-500"></div>
|
|
289
|
+
<div className="w-3 h-3 rounded-full bg-green-500"></div>
|
|
290
|
+
</div>
|
|
291
|
+
<span className="text-xs font-medium text-gray-400 uppercase tracking-wider">Response</span>
|
|
292
|
+
<span className={`px-2 py-0.5 rounded text-xs font-semibold ${
|
|
293
|
+
response.status >= 200 && response.status < 300
|
|
294
|
+
? 'bg-green-900/30 text-green-400'
|
|
295
|
+
: response.status >= 400
|
|
296
|
+
? 'bg-red-900/30 text-red-400'
|
|
297
|
+
: 'bg-yellow-900/30 text-yellow-400'
|
|
298
|
+
}`}>
|
|
299
|
+
{response.status || 'Error'}
|
|
300
|
+
</span>
|
|
301
|
+
</div>
|
|
302
|
+
<button
|
|
303
|
+
onClick={() => setShowResponse(false)}
|
|
304
|
+
className="px-2 py-1 text-xs text-gray-400 hover:text-white transition-colors rounded hover:bg-gray-700/50"
|
|
305
|
+
title="Close"
|
|
306
|
+
>
|
|
307
|
+
ā
|
|
308
|
+
</button>
|
|
309
|
+
</div>
|
|
310
|
+
<div className="p-5 overflow-x-auto bg-gray-950">
|
|
311
|
+
<pre className="text-sm text-gray-100 font-mono leading-relaxed whitespace-pre-wrap">
|
|
312
|
+
<code className="block">
|
|
313
|
+
{typeof response.data === 'string'
|
|
314
|
+
? response.data
|
|
315
|
+
: JSON.stringify(response.data, null, 2)}
|
|
316
|
+
</code>
|
|
317
|
+
</pre>
|
|
318
|
+
</div>
|
|
319
|
+
</div>
|
|
320
|
+
)}
|
|
321
|
+
|
|
169
322
|
{/* Toast Notification */}
|
|
170
323
|
{showToast && (
|
|
171
324
|
<div className="fixed bottom-4 right-4 bg-green-600 text-white px-4 py-3 rounded-lg shadow-lg flex items-center gap-2 z-50 animate-fade-in">
|