@theihtisham/devtools-with-cloud 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 (150) hide show
  1. package/.env.example +15 -0
  2. package/LICENSE +21 -0
  3. package/README.md +73 -0
  4. package/docker-compose.yml +23 -0
  5. package/jest.config.js +7 -0
  6. package/next-env.d.ts +5 -0
  7. package/next.config.mjs +22 -0
  8. package/package.json +82 -0
  9. package/postcss.config.js +6 -0
  10. package/prisma/schema.prisma +105 -0
  11. package/prisma/seed.ts +211 -0
  12. package/src/app/(app)/ai/page.tsx +122 -0
  13. package/src/app/(app)/collections/page.tsx +155 -0
  14. package/src/app/(app)/environments/page.tsx +96 -0
  15. package/src/app/(app)/history/page.tsx +107 -0
  16. package/src/app/(app)/import/page.tsx +102 -0
  17. package/src/app/(app)/layout.tsx +60 -0
  18. package/src/app/(app)/settings/page.tsx +79 -0
  19. package/src/app/(app)/workspace/page.tsx +284 -0
  20. package/src/app/api/ai/discover/route.ts +17 -0
  21. package/src/app/api/ai/explain/route.ts +29 -0
  22. package/src/app/api/ai/generate-tests/route.ts +37 -0
  23. package/src/app/api/ai/suggest/route.ts +29 -0
  24. package/src/app/api/collections/[id]/route.ts +66 -0
  25. package/src/app/api/collections/route.ts +48 -0
  26. package/src/app/api/environments/route.ts +40 -0
  27. package/src/app/api/export/openapi/route.ts +17 -0
  28. package/src/app/api/export/postman/route.ts +18 -0
  29. package/src/app/api/import/curl/route.ts +18 -0
  30. package/src/app/api/import/har/route.ts +20 -0
  31. package/src/app/api/import/openapi/route.ts +21 -0
  32. package/src/app/api/import/postman/route.ts +21 -0
  33. package/src/app/api/proxy/route.ts +35 -0
  34. package/src/app/api/requests/[id]/execute/route.ts +85 -0
  35. package/src/app/api/requests/[id]/history/route.ts +23 -0
  36. package/src/app/api/requests/[id]/route.ts +66 -0
  37. package/src/app/api/requests/route.ts +49 -0
  38. package/src/app/api/workspaces/route.ts +38 -0
  39. package/src/app/globals.css +99 -0
  40. package/src/app/layout.tsx +24 -0
  41. package/src/app/page.tsx +182 -0
  42. package/src/components/ai/ai-panel.tsx +65 -0
  43. package/src/components/ai/code-explainer.tsx +51 -0
  44. package/src/components/ai/endpoint-discovery.tsx +62 -0
  45. package/src/components/ai/test-generator.tsx +49 -0
  46. package/src/components/collections/collection-actions.tsx +36 -0
  47. package/src/components/collections/collection-tree.tsx +55 -0
  48. package/src/components/collections/folder-creator.tsx +54 -0
  49. package/src/components/landing/comparison.tsx +43 -0
  50. package/src/components/landing/cta.tsx +16 -0
  51. package/src/components/landing/features.tsx +24 -0
  52. package/src/components/landing/hero.tsx +23 -0
  53. package/src/components/response/body-viewer.tsx +33 -0
  54. package/src/components/response/headers-viewer.tsx +23 -0
  55. package/src/components/response/status-badge.tsx +25 -0
  56. package/src/components/response/test-results.tsx +50 -0
  57. package/src/components/response/timing-chart.tsx +39 -0
  58. package/src/components/ui/badge.tsx +24 -0
  59. package/src/components/ui/button.tsx +32 -0
  60. package/src/components/ui/code-editor.tsx +51 -0
  61. package/src/components/ui/dialog.tsx +56 -0
  62. package/src/components/ui/dropdown.tsx +63 -0
  63. package/src/components/ui/input.tsx +22 -0
  64. package/src/components/ui/key-value-editor.tsx +75 -0
  65. package/src/components/ui/select.tsx +24 -0
  66. package/src/components/ui/tabs.tsx +85 -0
  67. package/src/components/ui/textarea.tsx +22 -0
  68. package/src/components/ui/toast.tsx +54 -0
  69. package/src/components/workspace/request-panel.tsx +38 -0
  70. package/src/components/workspace/response-panel.tsx +81 -0
  71. package/src/components/workspace/sidebar.tsx +52 -0
  72. package/src/components/workspace/split-pane.tsx +49 -0
  73. package/src/components/workspace/tabs/auth-tab.tsx +94 -0
  74. package/src/components/workspace/tabs/body-tab.tsx +41 -0
  75. package/src/components/workspace/tabs/headers-tab.tsx +23 -0
  76. package/src/components/workspace/tabs/params-tab.tsx +23 -0
  77. package/src/components/workspace/tabs/pre-request-tab.tsx +26 -0
  78. package/src/components/workspace/url-bar.tsx +53 -0
  79. package/src/hooks/use-ai.ts +115 -0
  80. package/src/hooks/use-collection.ts +71 -0
  81. package/src/hooks/use-environment.ts +73 -0
  82. package/src/hooks/use-request.ts +111 -0
  83. package/src/lib/ai/endpoint-discovery.ts +158 -0
  84. package/src/lib/ai/explainer.ts +127 -0
  85. package/src/lib/ai/suggester.ts +164 -0
  86. package/src/lib/ai/test-generator.ts +161 -0
  87. package/src/lib/auth/api-key.ts +28 -0
  88. package/src/lib/auth/aws-sig.ts +131 -0
  89. package/src/lib/auth/basic.ts +17 -0
  90. package/src/lib/auth/bearer.ts +15 -0
  91. package/src/lib/auth/oauth2.ts +155 -0
  92. package/src/lib/auth/types.ts +16 -0
  93. package/src/lib/db/client.ts +15 -0
  94. package/src/lib/env/manager.ts +32 -0
  95. package/src/lib/env/resolver.ts +30 -0
  96. package/src/lib/exporters/openapi.ts +193 -0
  97. package/src/lib/exporters/postman.ts +140 -0
  98. package/src/lib/graphql/builder.ts +249 -0
  99. package/src/lib/graphql/formatter.ts +147 -0
  100. package/src/lib/graphql/index.ts +43 -0
  101. package/src/lib/graphql/introspection.ts +175 -0
  102. package/src/lib/graphql/types.ts +99 -0
  103. package/src/lib/graphql/validator.ts +216 -0
  104. package/src/lib/http/client.ts +112 -0
  105. package/src/lib/http/proxy.ts +83 -0
  106. package/src/lib/http/request-builder.ts +214 -0
  107. package/src/lib/http/response-parser.ts +106 -0
  108. package/src/lib/http/timing.ts +63 -0
  109. package/src/lib/importers/curl-parser.ts +346 -0
  110. package/src/lib/importers/har-parser.ts +128 -0
  111. package/src/lib/importers/openapi.ts +324 -0
  112. package/src/lib/importers/postman.ts +312 -0
  113. package/src/lib/test-runner/assertions.ts +163 -0
  114. package/src/lib/test-runner/reporter.ts +90 -0
  115. package/src/lib/test-runner/runner.ts +69 -0
  116. package/src/lib/utils/api-response.ts +85 -0
  117. package/src/lib/utils/cn.ts +6 -0
  118. package/src/lib/utils/content-type.ts +123 -0
  119. package/src/lib/utils/download.ts +53 -0
  120. package/src/lib/utils/errors.ts +92 -0
  121. package/src/lib/utils/format.ts +142 -0
  122. package/src/lib/utils/syntax-highlight.ts +108 -0
  123. package/src/lib/utils/validation.ts +231 -0
  124. package/src/lib/websocket/client.ts +182 -0
  125. package/src/lib/websocket/frames.ts +96 -0
  126. package/src/lib/websocket/history.ts +121 -0
  127. package/src/lib/websocket/index.ts +25 -0
  128. package/src/lib/websocket/types.ts +57 -0
  129. package/src/types/ai.ts +28 -0
  130. package/src/types/collection.ts +24 -0
  131. package/src/types/environment.ts +16 -0
  132. package/src/types/request.ts +54 -0
  133. package/src/types/response.ts +37 -0
  134. package/tailwind.config.ts +82 -0
  135. package/tests/lib/env/resolver.test.ts +108 -0
  136. package/tests/lib/graphql/builder.test.ts +349 -0
  137. package/tests/lib/graphql/formatter.test.ts +99 -0
  138. package/tests/lib/http/request-builder.test.ts +160 -0
  139. package/tests/lib/http/response-parser.test.ts +150 -0
  140. package/tests/lib/http/timing.test.ts +188 -0
  141. package/tests/lib/importers/curl-parser.test.ts +245 -0
  142. package/tests/lib/test-runner/assertions.test.ts +342 -0
  143. package/tests/lib/utils/cn.test.ts +46 -0
  144. package/tests/lib/utils/content-type.test.ts +175 -0
  145. package/tests/lib/utils/format.test.ts +188 -0
  146. package/tests/lib/utils/validation.test.ts +237 -0
  147. package/tests/lib/websocket/history.test.ts +186 -0
  148. package/tsconfig.json +29 -0
  149. package/tsconfig.tsbuildinfo +1 -0
  150. package/vitest.config.ts +21 -0
@@ -0,0 +1,71 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect, useCallback } from 'react';
4
+
5
+ interface Collection {
6
+ id: string;
7
+ name: string;
8
+ description?: string;
9
+ parentId?: string;
10
+ sortOrder: number;
11
+ }
12
+
13
+ interface UseCollectionReturn {
14
+ collections: Collection[];
15
+ loading: boolean;
16
+ error: string | null;
17
+ fetchCollections: () => Promise<void>;
18
+ createCollection: (name: string, description?: string, workspaceId?: string) => Promise<void>;
19
+ deleteCollection: (id: string) => Promise<void>;
20
+ }
21
+
22
+ export function useCollection(workspaceId?: string): UseCollectionReturn {
23
+ const [collections, setCollections] = useState<Collection[]>([]);
24
+ const [loading, setLoading] = useState(true);
25
+ const [error, setError] = useState<string | null>(null);
26
+
27
+ const fetchCollections = useCallback(async () => {
28
+ setLoading(true);
29
+ try {
30
+ const params = workspaceId ? `?workspaceId=${workspaceId}` : '';
31
+ const res = await fetch(`/api/collections${params}`);
32
+ const data = await res.json();
33
+ if (data.success) setCollections(data.data);
34
+ } catch (err) {
35
+ setError(err instanceof Error ? err.message : 'Failed to load collections');
36
+ } finally {
37
+ setLoading(false);
38
+ }
39
+ }, [workspaceId]);
40
+
41
+ const createCollection = useCallback(async (name: string, description?: string, wsId?: string) => {
42
+ try {
43
+ const res = await fetch('/api/collections', {
44
+ method: 'POST',
45
+ headers: { 'Content-Type': 'application/json' },
46
+ body: JSON.stringify({ name, description, workspaceId: wsId ?? workspaceId ?? 'default' }),
47
+ });
48
+ const data = await res.json();
49
+ if (data.success) {
50
+ setCollections((prev) => [...prev, data.data]);
51
+ }
52
+ } catch (err) {
53
+ setError(err instanceof Error ? err.message : 'Failed to create collection');
54
+ }
55
+ }, [workspaceId]);
56
+
57
+ const deleteCollection = useCallback(async (id: string) => {
58
+ try {
59
+ await fetch(`/api/collections/${id}`, { method: 'DELETE' });
60
+ setCollections((prev) => prev.filter((c) => c.id !== id));
61
+ } catch (err) {
62
+ setError(err instanceof Error ? err.message : 'Failed to delete collection');
63
+ }
64
+ }, []);
65
+
66
+ useEffect(() => {
67
+ fetchCollections();
68
+ }, [fetchCollections]);
69
+
70
+ return { collections, loading, error, fetchCollections, createCollection, deleteCollection };
71
+ }
@@ -0,0 +1,73 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect, useCallback } from 'react';
4
+
5
+ interface Environment {
6
+ id: string;
7
+ name: string;
8
+ isDefault: boolean;
9
+ variables: Array<{
10
+ key: string;
11
+ value: string;
12
+ type: string;
13
+ enabled: boolean;
14
+ }>;
15
+ }
16
+
17
+ interface UseEnvironmentReturn {
18
+ environments: Environment[];
19
+ activeEnvironment: Environment | null;
20
+ variables: Record<string, string>;
21
+ loading: boolean;
22
+ setActiveEnvironment: (env: Environment | null) => void;
23
+ refresh: () => Promise<void>;
24
+ }
25
+
26
+ export function useEnvironment(workspaceId?: string): UseEnvironmentReturn {
27
+ const [environments, setEnvironments] = useState<Environment[]>([]);
28
+ const [activeEnvironment, setActiveEnvironment] = useState<Environment | null>(null);
29
+ const [loading, setLoading] = useState(true);
30
+
31
+ const variables = useCallback((): Record<string, string> => {
32
+ const vars: Record<string, string> = {};
33
+ if (activeEnvironment) {
34
+ for (const v of activeEnvironment.variables) {
35
+ if (v.enabled) vars[v.key] = v.value;
36
+ }
37
+ }
38
+ return vars;
39
+ }, [activeEnvironment]);
40
+
41
+ const refresh = useCallback(async () => {
42
+ setLoading(true);
43
+ try {
44
+ const params = workspaceId ? `?workspaceId=${workspaceId}` : '';
45
+ const res = await fetch(`/api/environments${params}`);
46
+ const data = await res.json();
47
+ if (data.success) {
48
+ setEnvironments(data.data);
49
+ const defaultEnv = data.data.find((e: Environment) => e.isDefault);
50
+ if (defaultEnv && !activeEnvironment) {
51
+ setActiveEnvironment(defaultEnv);
52
+ }
53
+ }
54
+ } catch {
55
+ // Handle error
56
+ } finally {
57
+ setLoading(false);
58
+ }
59
+ }, [workspaceId, activeEnvironment]);
60
+
61
+ useEffect(() => {
62
+ refresh();
63
+ }, [refresh]);
64
+
65
+ return {
66
+ environments,
67
+ activeEnvironment,
68
+ variables: variables(),
69
+ loading,
70
+ setActiveEnvironment,
71
+ refresh,
72
+ };
73
+ }
@@ -0,0 +1,111 @@
1
+ 'use client';
2
+
3
+ import { useState, useCallback } from 'react';
4
+ import type { HttpMethod, KeyValue, BodyType, RequestBody, AuthConfig } from '@/lib/utils/validation';
5
+ import type { ApiResponse } from '@/types/response';
6
+
7
+ interface UseRequestReturn {
8
+ method: HttpMethod;
9
+ url: string;
10
+ headers: KeyValue[];
11
+ params: KeyValue[];
12
+ body: string;
13
+ bodyType: BodyType;
14
+ auth: AuthConfig | null;
15
+ response: ApiResponse | null;
16
+ loading: boolean;
17
+ error: string | null;
18
+ setMethod: (method: HttpMethod) => void;
19
+ setUrl: (url: string) => void;
20
+ setHeaders: (headers: KeyValue[]) => void;
21
+ setParams: (params: KeyValue[]) => void;
22
+ setBody: (body: string) => void;
23
+ setBodyType: (bodyType: BodyType) => void;
24
+ setAuth: (auth: AuthConfig | null) => void;
25
+ send: () => Promise<void>;
26
+ reset: () => void;
27
+ }
28
+
29
+ export function useRequest(initialUrl: string = '', initialMethod: HttpMethod = 'GET'): UseRequestReturn {
30
+ const [method, setMethod] = useState<HttpMethod>(initialMethod);
31
+ const [url, setUrl] = useState(initialUrl);
32
+ const [headers, setHeaders] = useState<KeyValue[]>([]);
33
+ const [params, setParams] = useState<KeyValue[]>([]);
34
+ const [body, setBody] = useState('');
35
+ const [bodyType, setBodyType] = useState<BodyType>('none');
36
+ const [auth, setAuth] = useState<AuthConfig | null>(null);
37
+ const [response, setResponse] = useState<ApiResponse | null>(null);
38
+ const [loading, setLoading] = useState(false);
39
+ const [error, setError] = useState<string | null>(null);
40
+
41
+ const send = useCallback(async () => {
42
+ if (!url.trim()) return;
43
+ setLoading(true);
44
+ setError(null);
45
+
46
+ try {
47
+ const headerMap: Record<string, string> = {};
48
+ for (const h of headers) {
49
+ if (h.enabled && h.key) headerMap[h.key] = h.value;
50
+ }
51
+
52
+ let reqBody: string | undefined;
53
+ if (bodyType !== 'none' && method !== 'GET' && method !== 'HEAD') {
54
+ reqBody = body;
55
+ if (bodyType === 'json' && !headerMap['Content-Type']) {
56
+ headerMap['Content-Type'] = 'application/json';
57
+ }
58
+ }
59
+
60
+ const start = performance.now();
61
+ const res = await fetch('/api/proxy', {
62
+ method: 'POST',
63
+ headers: { 'Content-Type': 'application/json' },
64
+ body: JSON.stringify({ url, method, headers: headerMap, body: reqBody }),
65
+ });
66
+
67
+ const data = await res.json();
68
+ const elapsed = Math.round(performance.now() - start);
69
+
70
+ const responseHeaders: Record<string, string> = {};
71
+ if (data.headers) {
72
+ for (const [key, value] of Object.entries(data.headers as Record<string, string>)) {
73
+ responseHeaders[key] = value;
74
+ }
75
+ }
76
+
77
+ setResponse({
78
+ status: data.status ?? res.status,
79
+ statusText: data.statusText ?? res.statusText,
80
+ headers: responseHeaders,
81
+ body: typeof data.body === 'string' ? data.body : JSON.stringify(data.body, null, 2),
82
+ bodySize: typeof data.body === 'string' ? new TextEncoder().encode(data.body).length : 0,
83
+ timing: { dns: 0, tcp: 0, tls: 0, firstByte: 0, total: elapsed },
84
+ url,
85
+ });
86
+ } catch (err) {
87
+ setError(err instanceof Error ? err.message : 'Request failed');
88
+ } finally {
89
+ setLoading(false);
90
+ }
91
+ }, [url, method, headers, body, bodyType]);
92
+
93
+ const reset = useCallback(() => {
94
+ setMethod('GET');
95
+ setUrl('');
96
+ setHeaders([]);
97
+ setParams([]);
98
+ setBody('');
99
+ setBodyType('none');
100
+ setAuth(null);
101
+ setResponse(null);
102
+ setError(null);
103
+ }, []);
104
+
105
+ return {
106
+ method, url, headers, params, body, bodyType, auth,
107
+ response, loading, error,
108
+ setMethod, setUrl, setHeaders, setParams, setBody, setBodyType, setAuth,
109
+ send, reset,
110
+ };
111
+ }
@@ -0,0 +1,158 @@
1
+ import type { HttpMethod } from '@/lib/utils/validation';
2
+
3
+ export interface DiscoveredEndpoint {
4
+ method: HttpMethod;
5
+ path: string;
6
+ name: string;
7
+ description?: string;
8
+ headers: { key: string; value: string; enabled: boolean }[];
9
+ params: { key: string; value: string; enabled: boolean }[];
10
+ body?: { raw?: string };
11
+ bodyType?: string;
12
+ }
13
+
14
+ /**
15
+ * Discover API endpoints from source code.
16
+ * Supports Express, Fastify, Next.js API routes, FastAPI, and more.
17
+ */
18
+ export function discoverEndpoints(
19
+ code: string,
20
+ framework: string = 'auto',
21
+ ): DiscoveredEndpoint[] {
22
+ if (framework === 'auto') {
23
+ framework = detectFramework(code);
24
+ }
25
+
26
+ switch (framework) {
27
+ case 'express':
28
+ return discoverExpressEndpoints(code);
29
+ case 'nextjs':
30
+ return discoverNextjsEndpoints(code);
31
+ case 'fastapi':
32
+ return discoverFastApiEndpoints(code);
33
+ default:
34
+ return discoverExpressEndpoints(code);
35
+ }
36
+ }
37
+
38
+ function detectFramework(code: string): string {
39
+ if (code.includes('express()') || code.includes('require("express")') || code.includes("require('express')")) {
40
+ return 'express';
41
+ }
42
+ if (code.includes('NextResponse') || code.includes('next/server')) {
43
+ return 'nextjs';
44
+ }
45
+ if (code.includes('@app.') || code.includes('@router.') || code.includes('from fastapi')) {
46
+ return 'fastapi';
47
+ }
48
+ if (code.includes('app.get') || code.includes('app.post') || code.includes('app.put') || code.includes('app.delete')) {
49
+ return 'express';
50
+ }
51
+ return 'express';
52
+ }
53
+
54
+ function discoverExpressEndpoints(code: string): DiscoveredEndpoint[] {
55
+ const endpoints: DiscoveredEndpoint[] = [];
56
+ const methodPattern = /app\.(get|post|put|patch|delete|head|options)\s*\(\s*['"`]([^'"`]+)['"`]/gi;
57
+ let match: RegExpExecArray | null;
58
+
59
+ while ((match = methodPattern.exec(code)) !== null) {
60
+ const method = match[1]?.toUpperCase() as HttpMethod;
61
+ const path = match[2] ?? '';
62
+ endpoints.push({
63
+ method,
64
+ path,
65
+ name: `${method} ${path}`,
66
+ headers: [],
67
+ params: [],
68
+ });
69
+ }
70
+
71
+ // Also match router patterns
72
+ const routerPattern = /router\.(get|post|put|patch|delete|head|options)\s*\(\s*['"`]([^'"`]+)['"`]/gi;
73
+ while ((match = routerPattern.exec(code)) !== null) {
74
+ const method = match[1]?.toUpperCase() as HttpMethod;
75
+ const path = match[2] ?? '';
76
+ endpoints.push({
77
+ method,
78
+ path,
79
+ name: `${method} ${path}`,
80
+ headers: [],
81
+ params: [],
82
+ });
83
+ }
84
+
85
+ return endpoints;
86
+ }
87
+
88
+ function discoverNextjsEndpoints(code: string): DiscoveredEndpoint[] {
89
+ const endpoints: DiscoveredEndpoint[] = [];
90
+
91
+ if (code.includes('GET') || code.includes('export async function GET')) {
92
+ endpoints.push({
93
+ method: 'GET',
94
+ path: '/api/...',
95
+ name: 'GET /api/...',
96
+ headers: [],
97
+ params: [],
98
+ });
99
+ }
100
+ if (code.includes('POST') || code.includes('export async function POST')) {
101
+ endpoints.push({
102
+ method: 'POST',
103
+ path: '/api/...',
104
+ name: 'POST /api/...',
105
+ headers: [],
106
+ params: [],
107
+ });
108
+ }
109
+ if (code.includes('PUT') || code.includes('export async function PUT')) {
110
+ endpoints.push({
111
+ method: 'PUT',
112
+ path: '/api/...',
113
+ name: 'PUT /api/...',
114
+ headers: [],
115
+ params: [],
116
+ });
117
+ }
118
+ if (code.includes('DELETE') || code.includes('export async function DELETE')) {
119
+ endpoints.push({
120
+ method: 'DELETE',
121
+ path: '/api/...',
122
+ name: 'DELETE /api/...',
123
+ headers: [],
124
+ params: [],
125
+ });
126
+ }
127
+ if (code.includes('PATCH') || code.includes('export async function PATCH')) {
128
+ endpoints.push({
129
+ method: 'PATCH',
130
+ path: '/api/...',
131
+ name: 'PATCH /api/...',
132
+ headers: [],
133
+ params: [],
134
+ });
135
+ }
136
+
137
+ return endpoints;
138
+ }
139
+
140
+ function discoverFastApiEndpoints(code: string): DiscoveredEndpoint[] {
141
+ const endpoints: DiscoveredEndpoint[] = [];
142
+ const pattern = /@(?:app|router)\.(get|post|put|patch|delete)\s*\(\s*["']([^"']+)["']/gi;
143
+ let match: RegExpExecArray | null;
144
+
145
+ while ((match = pattern.exec(code)) !== null) {
146
+ const method = match[1]?.toUpperCase() as HttpMethod;
147
+ const path = match[2] ?? '';
148
+ endpoints.push({
149
+ method,
150
+ path,
151
+ name: `${method} ${path}`,
152
+ headers: [],
153
+ params: [],
154
+ });
155
+ }
156
+
157
+ return endpoints;
158
+ }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * AI response explainer - explains API responses, status codes, and headers.
3
+ */
4
+
5
+ interface ExplainRequest {
6
+ response: {
7
+ status: number;
8
+ statusText: string;
9
+ headers: Record<string, string>;
10
+ body: string;
11
+ };
12
+ question?: string;
13
+ }
14
+
15
+ /**
16
+ * Explain an API response. Uses AI if available, falls back to local explanation.
17
+ */
18
+ export async function explainResponse(
19
+ req: ExplainRequest,
20
+ apiKey?: string,
21
+ baseUrl?: string,
22
+ model?: string,
23
+ ): Promise<string> {
24
+ const localExplanation = generateLocalExplanation(req);
25
+
26
+ if (!apiKey) {
27
+ return localExplanation;
28
+ }
29
+
30
+ try {
31
+ const openaiUrl = `${baseUrl ?? 'https://api.openai.com/v1'}/chat/completions`;
32
+
33
+ const prompt = `Explain this API response:
34
+
35
+ Status: ${req.response.status} ${req.response.statusText}
36
+ Headers: ${JSON.stringify(req.response.headers, null, 2)}
37
+ Body: ${req.response.body.slice(0, 2000)}
38
+
39
+ ${req.question ? `User question: ${req.question}` : 'Provide a comprehensive explanation.'}`;
40
+
41
+ const res = await fetch(openaiUrl, {
42
+ method: 'POST',
43
+ headers: {
44
+ 'Content-Type': 'application/json',
45
+ 'Authorization': `Bearer ${apiKey}`,
46
+ },
47
+ body: JSON.stringify({
48
+ model: model ?? 'gpt-4o',
49
+ messages: [
50
+ { role: 'system', content: 'You are an API expert. Explain responses clearly and concisely.' },
51
+ { role: 'user', content: prompt },
52
+ ],
53
+ temperature: 0.3,
54
+ max_tokens: 1000,
55
+ }),
56
+ });
57
+
58
+ if (!res.ok) return localExplanation;
59
+
60
+ const data = await res.json() as { choices: Array<{ message: { content: string } }> };
61
+ return data.choices?.[0]?.message?.content ?? localExplanation;
62
+ } catch {
63
+ return localExplanation;
64
+ }
65
+ }
66
+
67
+ function generateLocalExplanation(req: ExplainRequest): string {
68
+ const parts: string[] = [];
69
+
70
+ // Status code explanation
71
+ parts.push(`**Status: ${req.response.status} ${req.response.statusText}**`);
72
+ parts.push(getStatusExplanation(req.response.status));
73
+
74
+ // Content type
75
+ const contentType = req.response.headers['content-type'];
76
+ if (contentType) {
77
+ parts.push(`\n**Content-Type:** ${contentType}`);
78
+ }
79
+
80
+ // Body analysis
81
+ try {
82
+ const json = JSON.parse(req.response.body);
83
+ if (Array.isArray(json)) {
84
+ parts.push(`\n**Body:** Array with ${json.length} items`);
85
+ } else if (typeof json === 'object' && json !== null) {
86
+ const keys = Object.keys(json);
87
+ parts.push(`\n**Body:** Object with keys: ${keys.join(', ')}`);
88
+ }
89
+ } catch {
90
+ if (req.response.body.length > 0) {
91
+ parts.push(`\n**Body:** ${req.response.body.length} bytes of response data`);
92
+ }
93
+ }
94
+
95
+ return parts.join('\n');
96
+ }
97
+
98
+ function getStatusExplanation(status: number): string {
99
+ if (status >= 200 && status < 300) {
100
+ return 'The request was successful.';
101
+ }
102
+ if (status === 301 || status === 302) {
103
+ return 'The resource has been moved. The client should follow the redirect.';
104
+ }
105
+ if (status === 304) {
106
+ return 'The resource has not been modified since the last request.';
107
+ }
108
+ if (status === 400) {
109
+ return 'Bad Request: The server could not understand the request due to invalid syntax.';
110
+ }
111
+ if (status === 401) {
112
+ return 'Unauthorized: Authentication is required to access this resource.';
113
+ }
114
+ if (status === 403) {
115
+ return 'Forbidden: You do not have permission to access this resource.';
116
+ }
117
+ if (status === 404) {
118
+ return 'Not Found: The requested resource does not exist.';
119
+ }
120
+ if (status === 429) {
121
+ return 'Too Many Requests: Rate limit exceeded. Try again later.';
122
+ }
123
+ if (status >= 500) {
124
+ return 'Server Error: The server encountered an error processing the request.';
125
+ }
126
+ return 'See HTTP status code documentation for details.';
127
+ }
@@ -0,0 +1,164 @@
1
+ /**
2
+ * AI code suggester - suggests improvements for API requests.
3
+ */
4
+
5
+ interface SuggestRequest {
6
+ request: {
7
+ method: string;
8
+ url: string;
9
+ headers: Record<string, string>;
10
+ body?: string;
11
+ bodyType?: string;
12
+ auth?: unknown;
13
+ };
14
+ response?: {
15
+ status: number;
16
+ statusText: string;
17
+ headers: Record<string, string>;
18
+ body: string;
19
+ duration: number;
20
+ };
21
+ }
22
+
23
+ export interface Suggestion {
24
+ category: 'security' | 'performance' | 'best-practice' | 'error-handling';
25
+ severity: 'info' | 'warning' | 'critical';
26
+ title: string;
27
+ description: string;
28
+ }
29
+
30
+ /**
31
+ * Generate improvement suggestions for a request/response.
32
+ */
33
+ export async function suggestImprovements(
34
+ req: SuggestRequest,
35
+ apiKey?: string,
36
+ baseUrl?: string,
37
+ model?: string,
38
+ ): Promise<Suggestion[]> {
39
+ const localSuggestions = generateLocalSuggestions(req);
40
+
41
+ if (!apiKey) {
42
+ return localSuggestions;
43
+ }
44
+
45
+ try {
46
+ const openaiUrl = `${baseUrl ?? 'https://api.openai.com/v1'}/chat/completions`;
47
+
48
+ const prompt = `Analyze this API request and suggest improvements:
49
+
50
+ Request:
51
+ - Method: ${req.request.method}
52
+ - URL: ${req.request.url}
53
+ - Headers: ${JSON.stringify(req.request.headers, null, 2)}
54
+ - Body Type: ${req.request.bodyType ?? 'none'}
55
+ - Auth: ${JSON.stringify(req.request.auth)}
56
+
57
+ ${req.response ? `Response:
58
+ - Status: ${req.response.status} ${req.response.statusText}
59
+ - Duration: ${req.response.duration}ms
60
+ - Body: ${req.response.body.slice(0, 1000)}` : ''}
61
+
62
+ Provide suggestions as JSON array with fields: category (security|performance|best-practice|error-handling), severity (info|warning|critical), title, description.`;
63
+
64
+ const res = await fetch(openaiUrl, {
65
+ method: 'POST',
66
+ headers: {
67
+ 'Content-Type': 'application/json',
68
+ 'Authorization': `Bearer ${apiKey}`,
69
+ },
70
+ body: JSON.stringify({
71
+ model: model ?? 'gpt-4o',
72
+ messages: [
73
+ { role: 'system', content: 'You are an API security and performance expert. Respond with valid JSON only.' },
74
+ { role: 'user', content: prompt },
75
+ ],
76
+ temperature: 0.3,
77
+ max_tokens: 1500,
78
+ }),
79
+ });
80
+
81
+ if (!res.ok) return localSuggestions;
82
+
83
+ const data = await res.json() as { choices: Array<{ message: { content: string } }> };
84
+ const content = data.choices?.[0]?.message?.content;
85
+ if (content) {
86
+ try {
87
+ const suggestions = JSON.parse(content) as Suggestion[];
88
+ if (Array.isArray(suggestions)) return suggestions;
89
+ } catch {
90
+ // Fall through to local
91
+ }
92
+ }
93
+
94
+ return localSuggestions;
95
+ } catch {
96
+ return localSuggestions;
97
+ }
98
+ }
99
+
100
+ function generateLocalSuggestions(req: SuggestRequest): Suggestion[] {
101
+ const suggestions: Suggestion[] = [];
102
+ const { request: r } = req;
103
+
104
+ // Security checks
105
+ if (r.url.startsWith('http://') && !r.url.includes('localhost')) {
106
+ suggestions.push({
107
+ category: 'security',
108
+ severity: 'warning',
109
+ title: 'Use HTTPS',
110
+ description: 'This request uses HTTP instead of HTTPS. Consider using HTTPS for secure communication.',
111
+ });
112
+ }
113
+
114
+ const hasAuth = r.headers['Authorization'] || r.headers['authorization'] || r.auth;
115
+ if (!hasAuth && r.method !== 'GET' && r.method !== 'HEAD' && r.method !== 'OPTIONS') {
116
+ suggestions.push({
117
+ category: 'security',
118
+ severity: 'info',
119
+ title: 'Consider adding authentication',
120
+ description: 'This mutating request does not include authentication headers.',
121
+ });
122
+ }
123
+
124
+ // Performance checks
125
+ if (req.response && req.response.duration > 2000) {
126
+ suggestions.push({
127
+ category: 'performance',
128
+ severity: 'warning',
129
+ title: 'Slow response time',
130
+ description: `Response took ${req.response.duration}ms. Consider optimizing the endpoint or adding caching.`,
131
+ });
132
+ }
133
+
134
+ // Best practices
135
+ if (!r.headers['Accept'] && !r.headers['accept']) {
136
+ suggestions.push({
137
+ category: 'best-practice',
138
+ severity: 'info',
139
+ title: 'Add Accept header',
140
+ description: 'Include an Accept header to specify the expected response format.',
141
+ });
142
+ }
143
+
144
+ if (r.bodyType === 'json' && (!r.headers['Content-Type'] && !r.headers['content-type'])) {
145
+ suggestions.push({
146
+ category: 'best-practice',
147
+ severity: 'warning',
148
+ title: 'Missing Content-Type header',
149
+ description: 'The request body is JSON but no Content-Type header is set.',
150
+ });
151
+ }
152
+
153
+ // Error handling
154
+ if (req.response && req.response.status >= 400) {
155
+ suggestions.push({
156
+ category: 'error-handling',
157
+ severity: 'critical',
158
+ title: `Request failed with ${req.response.status}`,
159
+ description: `The server returned ${req.response.status} ${req.response.statusText}. Check the request parameters and try again.`,
160
+ });
161
+ }
162
+
163
+ return suggestions;
164
+ }