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.
@@ -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 url = this.apiUrl.trim();
151
- if (!url.startsWith('http://') && !url.startsWith('https://')) {
152
- throw new Error(`Invalid API URL: "${url}". URL must start with http:// or https://`);
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
- const response = await fetch(url, {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zero-doc",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "description": "Zero-Config API Documentation Generator - Generate beautiful API docs from your code automatically",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -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
- <button
85
- onClick={() => copyToClipboard(curlCommand)}
86
- 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"
87
- title="Copy to clipboard"
88
- >
89
- šŸ“‹ Copy
90
- </button>
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">