@hustle-together/api-dev-tools 3.11.1 → 3.12.2
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/.claude/agents/code-reviewer.md +170 -0
- package/.claude/agents/docs-generator.md +80 -0
- package/.claude/agents/implementation-reviewer.md +119 -0
- package/.claude/agents/parallel-researcher.md +52 -0
- package/.claude/agents/research-validator.md +116 -0
- package/.claude/agents/schema-generator.md +70 -0
- package/.claude/agents/test-writer.md +104 -0
- package/.claude/api-dev-state.json +305 -56
- package/.claude/commands/README.md +21 -10
- package/.claude/commands/add-command.md +8 -5
- package/.claude/commands/api-create.md +36 -25
- package/.claude/commands/api-env.md +1 -0
- package/.claude/commands/api-interview.md +32 -19
- package/.claude/commands/api-research.md +47 -21
- package/.claude/commands/api-status.md +21 -1
- package/.claude/commands/api-verify.md +14 -13
- package/.claude/commands/beepboop.md +4 -5
- package/.claude/commands/busycommit.md +2 -3
- package/.claude/commands/commit.md +2 -3
- package/.claude/commands/cycle.md +2 -7
- package/.claude/commands/gap.md +2 -3
- package/.claude/commands/green.md +2 -7
- package/.claude/commands/issue.md +3 -8
- package/.claude/commands/ntfy-setup.md +91 -0
- package/.claude/commands/ntfy-test.md +74 -0
- package/.claude/commands/plan.md +2 -3
- package/.claude/commands/pr.md +2 -3
- package/.claude/commands/publish.md +40 -0
- package/.claude/commands/red.md +2 -7
- package/.claude/commands/refactor.md +2 -7
- package/.claude/commands/spike.md +2 -7
- package/.claude/commands/summarize.md +2 -3
- package/.claude/commands/tdd.md +2 -7
- package/.claude/commands/worktree-add.md +208 -216
- package/.claude/commands/worktree-cleanup.md +172 -178
- package/.claude/settings.json +63 -12
- package/.claude/settings.local.json +2 -1
- package/.claude-plugin/marketplace.json +2 -11
- package/.skills/README.md +55 -53
- package/.skills/_shared/settings.json +1 -1
- package/.skills/add-command/SKILL.md +10 -5
- package/.skills/api-create/SKILL.md +146 -35
- package/.skills/api-env/SKILL.md +1 -0
- package/.skills/api-interview/SKILL.md +32 -19
- package/.skills/api-research/SKILL.md +47 -21
- package/.skills/api-status/SKILL.md +21 -1
- package/.skills/api-verify/SKILL.md +14 -13
- package/.skills/beepboop/SKILL.md +6 -5
- package/.skills/busycommit/SKILL.md +4 -3
- package/.skills/commit/SKILL.md +4 -3
- package/.skills/cycle/SKILL.md +4 -7
- package/.skills/gap/SKILL.md +4 -3
- package/.skills/green/SKILL.md +4 -7
- package/.skills/issue/SKILL.md +5 -8
- package/.skills/plan/SKILL.md +4 -3
- package/.skills/pr/SKILL.md +4 -3
- package/.skills/publish/SKILL.md +160 -0
- package/.skills/red/SKILL.md +4 -7
- package/.skills/refactor/SKILL.md +4 -7
- package/.skills/spike/SKILL.md +4 -7
- package/.skills/summarize/SKILL.md +4 -3
- package/.skills/tdd/SKILL.md +4 -7
- package/.skills/update-todos/SKILL.md +22 -0
- package/.skills/worktree-add/SKILL.md +210 -216
- package/.skills/worktree-cleanup/SKILL.md +183 -187
- package/CHANGELOG.md +97 -79
- package/README.md +161 -7142
- package/bin/cli.js +448 -805
- package/commands/README.md +66 -31
- package/commands/add-command.md +8 -5
- package/commands/beepboop.md +4 -5
- package/commands/busycommit.md +2 -3
- package/commands/commit.md +2 -3
- package/commands/cycle.md +2 -7
- package/commands/gap.md +2 -3
- package/commands/green.md +2 -7
- package/commands/hustle-api-continue.md +8 -5
- package/commands/hustle-api-create.md +70 -29
- package/commands/hustle-api-env.md +1 -0
- package/commands/hustle-api-interview.md +32 -19
- package/commands/hustle-api-research.md +47 -21
- package/commands/hustle-api-sessions.md +8 -7
- package/commands/hustle-api-status.md +21 -1
- package/commands/hustle-api-verify.md +14 -13
- package/commands/hustle-combine.md +488 -241
- package/commands/hustle-ui-create-page.md +113 -50
- package/commands/hustle-ui-create.md +179 -26
- package/commands/issue.md +3 -8
- package/commands/plan.md +2 -3
- package/commands/pr.md +2 -3
- package/commands/red.md +2 -7
- package/commands/refactor.md +2 -7
- package/commands/spike.md +2 -7
- package/commands/summarize.md +2 -3
- package/commands/tdd.md +2 -7
- package/commands/worktree-add.md +208 -216
- package/commands/worktree-cleanup.md +172 -178
- package/hooks/api-workflow-check.py +5 -3
- package/hooks/enforce-component-type-confirm.py +97 -0
- package/hooks/lib/__init__.py +1 -0
- package/hooks/lib/greptile.py +355 -0
- package/hooks/lib/ntfy.py +209 -0
- package/hooks/notify-input-needed.py +73 -0
- package/hooks/notify-phase-complete.py +90 -0
- package/hooks/run-code-review.py +246 -0
- package/hooks/track-token-usage.py +121 -0
- package/package.json +13 -3
- package/scripts/collect-test-results.ts +102 -77
- package/scripts/extract-parameters.ts +112 -70
- package/scripts/generate-test-manifest.ts +118 -77
- package/templates/.env.example +57 -0
- package/templates/BRAND_GUIDE.md +92 -52
- package/templates/CLAUDE-SECTION.md +40 -37
- package/templates/SPEC.json +186 -38
- package/templates/api-dev-state.json +33 -4
- package/templates/api-showcase/_components/APICard.tsx +22 -18
- package/templates/api-showcase/_components/APIModal.tsx +110 -64
- package/templates/api-showcase/_components/APIShowcase.tsx +53 -35
- package/templates/api-showcase/_components/APITester.tsx +128 -67
- package/templates/api-showcase/page.tsx +4 -4
- package/templates/api-test/page.tsx +51 -30
- package/templates/api-test/test-structure/route.ts +43 -34
- package/templates/component/Component.stories.tsx +41 -39
- package/templates/component/Component.test.tsx +96 -78
- package/templates/component/Component.tsx +63 -52
- package/templates/component/Component.types.ts +10 -6
- package/templates/component/Component.visual.spec.ts +170 -0
- package/templates/component/index.ts +2 -2
- package/templates/dev-tools/_components/DevToolsLanding.tsx +8 -8
- package/templates/dev-tools/page.tsx +4 -3
- package/templates/mcp-servers.json +30 -2
- package/templates/page/page.e2e.test.ts +56 -48
- package/templates/page/page.tsx +3 -3
- package/templates/shared/HeroHeader.tsx +16 -15
- package/templates/shared/index.ts +1 -1
- package/templates/ui-showcase/_components/PreviewCard.tsx +20 -20
- package/templates/ui-showcase/_components/PreviewModal.tsx +149 -108
- package/templates/ui-showcase/_components/UIShowcase.tsx +43 -35
- package/templates/ui-showcase/page.tsx +4 -4
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
"use client";
|
|
2
2
|
|
|
3
|
-
import { useEffect, useCallback, useState } from
|
|
4
|
-
import { APITester } from
|
|
3
|
+
import { useEffect, useCallback, useState } from "react";
|
|
4
|
+
import { APITester } from "./APITester";
|
|
5
5
|
|
|
6
6
|
interface RegistryAPI {
|
|
7
7
|
name: string;
|
|
@@ -14,15 +14,18 @@ interface RegistryAPI {
|
|
|
14
14
|
status?: string;
|
|
15
15
|
combines?: string[];
|
|
16
16
|
flow_type?: string;
|
|
17
|
-
endpoints?: Record<
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
endpoints?: Record<
|
|
18
|
+
string,
|
|
19
|
+
{
|
|
20
|
+
methods: string[];
|
|
21
|
+
description?: string;
|
|
22
|
+
}
|
|
23
|
+
>;
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
interface APIModalProps {
|
|
24
27
|
id: string;
|
|
25
|
-
type:
|
|
28
|
+
type: "api" | "combined";
|
|
26
29
|
data: RegistryAPI;
|
|
27
30
|
onClose: () => void;
|
|
28
31
|
}
|
|
@@ -41,31 +44,35 @@ interface APIModalProps {
|
|
|
41
44
|
* Created with Hustle API Dev Tools (v3.9.2)
|
|
42
45
|
*/
|
|
43
46
|
export function APIModal({ id, type, data, onClose }: APIModalProps) {
|
|
44
|
-
const [activeTab, setActiveTab] = useState<
|
|
47
|
+
const [activeTab, setActiveTab] = useState<"try-it" | "docs" | "curl">(
|
|
48
|
+
"try-it",
|
|
49
|
+
);
|
|
45
50
|
const [selectedEndpoint, setSelectedEndpoint] = useState<string | null>(null);
|
|
46
51
|
|
|
47
52
|
// Close on Escape key
|
|
48
53
|
const handleKeyDown = useCallback(
|
|
49
54
|
(e: KeyboardEvent) => {
|
|
50
|
-
if (e.key ===
|
|
55
|
+
if (e.key === "Escape") {
|
|
51
56
|
onClose();
|
|
52
57
|
}
|
|
53
58
|
},
|
|
54
|
-
[onClose]
|
|
59
|
+
[onClose],
|
|
55
60
|
);
|
|
56
61
|
|
|
57
62
|
useEffect(() => {
|
|
58
|
-
document.addEventListener(
|
|
59
|
-
document.body.style.overflow =
|
|
63
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
64
|
+
document.body.style.overflow = "hidden";
|
|
60
65
|
|
|
61
66
|
return () => {
|
|
62
|
-
document.removeEventListener(
|
|
63
|
-
document.body.style.overflow =
|
|
67
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
68
|
+
document.body.style.overflow = "";
|
|
64
69
|
};
|
|
65
70
|
}, [handleKeyDown]);
|
|
66
71
|
|
|
67
72
|
// Get endpoints - either from endpoints object or default single endpoint
|
|
68
|
-
const endpoints = data.endpoints || {
|
|
73
|
+
const endpoints = data.endpoints || {
|
|
74
|
+
default: { methods: data.methods || ["POST"] },
|
|
75
|
+
};
|
|
69
76
|
const endpointKeys = Object.keys(endpoints);
|
|
70
77
|
const hasMultipleEndpoints = endpointKeys.length > 1;
|
|
71
78
|
|
|
@@ -76,13 +83,18 @@ export function APIModal({ id, type, data, onClose }: APIModalProps) {
|
|
|
76
83
|
}
|
|
77
84
|
}, [endpointKeys, selectedEndpoint]);
|
|
78
85
|
|
|
79
|
-
const currentEndpoint = selectedEndpoint
|
|
80
|
-
|
|
81
|
-
|
|
86
|
+
const currentEndpoint = selectedEndpoint
|
|
87
|
+
? endpoints[selectedEndpoint]
|
|
88
|
+
: endpoints[endpointKeys[0]];
|
|
89
|
+
const methods = currentEndpoint?.methods || data.methods || ["POST"];
|
|
90
|
+
const baseUrl =
|
|
91
|
+
typeof window !== "undefined"
|
|
92
|
+
? window.location.origin
|
|
93
|
+
: "http://localhost:3000";
|
|
82
94
|
|
|
83
95
|
// Build endpoint path
|
|
84
96
|
const getEndpointPath = () => {
|
|
85
|
-
if (selectedEndpoint && selectedEndpoint !==
|
|
97
|
+
if (selectedEndpoint && selectedEndpoint !== "default") {
|
|
86
98
|
return `/api/v2/${id}/${selectedEndpoint}`;
|
|
87
99
|
}
|
|
88
100
|
return `/api/v2/${id}`;
|
|
@@ -92,7 +104,7 @@ export function APIModal({ id, type, data, onClose }: APIModalProps) {
|
|
|
92
104
|
|
|
93
105
|
// Generate curl example
|
|
94
106
|
const generateCurl = (method: string) => {
|
|
95
|
-
if (method ===
|
|
107
|
+
if (method === "GET") {
|
|
96
108
|
return `curl -X GET "${baseUrl}${endpoint}"`;
|
|
97
109
|
}
|
|
98
110
|
return `curl -X ${method} "${baseUrl}${endpoint}" \\
|
|
@@ -123,10 +135,13 @@ export function APIModal({ id, type, data, onClose }: APIModalProps) {
|
|
|
123
135
|
<div className="flex items-center gap-4">
|
|
124
136
|
<div>
|
|
125
137
|
<div className="flex items-center gap-2">
|
|
126
|
-
<h2
|
|
138
|
+
<h2
|
|
139
|
+
id="modal-title"
|
|
140
|
+
className="text-xl font-bold text-black dark:text-white"
|
|
141
|
+
>
|
|
127
142
|
{data.name || id}
|
|
128
143
|
</h2>
|
|
129
|
-
{type ===
|
|
144
|
+
{type === "combined" && (
|
|
130
145
|
<span className="border border-purple-600 bg-purple-100 px-2 py-0.5 text-xs font-medium text-purple-800 dark:bg-purple-900 dark:text-purple-200">
|
|
131
146
|
Combined
|
|
132
147
|
</span>
|
|
@@ -145,13 +160,13 @@ export function APIModal({ id, type, data, onClose }: APIModalProps) {
|
|
|
145
160
|
<span
|
|
146
161
|
key={method}
|
|
147
162
|
className={`px-2 py-1 text-xs font-medium ${
|
|
148
|
-
method ===
|
|
149
|
-
?
|
|
150
|
-
: method ===
|
|
151
|
-
?
|
|
152
|
-
: method ===
|
|
153
|
-
?
|
|
154
|
-
:
|
|
163
|
+
method === "GET"
|
|
164
|
+
? "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200"
|
|
165
|
+
: method === "POST"
|
|
166
|
+
? "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200"
|
|
167
|
+
: method === "DELETE"
|
|
168
|
+
? "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200"
|
|
169
|
+
: "bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200"
|
|
155
170
|
}`}
|
|
156
171
|
>
|
|
157
172
|
{method}
|
|
@@ -195,8 +210,8 @@ export function APIModal({ id, type, data, onClose }: APIModalProps) {
|
|
|
195
210
|
onClick={() => setSelectedEndpoint(key)}
|
|
196
211
|
className={`border-2 px-3 py-1.5 text-sm font-medium transition-colors ${
|
|
197
212
|
selectedEndpoint === key
|
|
198
|
-
?
|
|
199
|
-
:
|
|
213
|
+
? "border-[#BA0C2F] bg-[#BA0C2F] text-white"
|
|
214
|
+
: "border-black bg-white text-black hover:border-[#BA0C2F] dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
|
200
215
|
}`}
|
|
201
216
|
>
|
|
202
217
|
/{key}
|
|
@@ -215,31 +230,31 @@ export function APIModal({ id, type, data, onClose }: APIModalProps) {
|
|
|
215
230
|
<div className="border-b-2 border-black px-6 dark:border-gray-600">
|
|
216
231
|
<nav className="flex gap-4">
|
|
217
232
|
<button
|
|
218
|
-
onClick={() => setActiveTab(
|
|
233
|
+
onClick={() => setActiveTab("try-it")}
|
|
219
234
|
className={`border-b-2 py-3 text-sm font-bold transition-colors ${
|
|
220
|
-
activeTab ===
|
|
221
|
-
?
|
|
222
|
-
:
|
|
235
|
+
activeTab === "try-it"
|
|
236
|
+
? "border-[#BA0C2F] text-[#BA0C2F]"
|
|
237
|
+
: "border-transparent text-gray-600 hover:text-black dark:text-gray-400 dark:hover:text-white"
|
|
223
238
|
}`}
|
|
224
239
|
>
|
|
225
240
|
Try It
|
|
226
241
|
</button>
|
|
227
242
|
<button
|
|
228
|
-
onClick={() => setActiveTab(
|
|
243
|
+
onClick={() => setActiveTab("docs")}
|
|
229
244
|
className={`border-b-2 py-3 text-sm font-bold transition-colors ${
|
|
230
|
-
activeTab ===
|
|
231
|
-
?
|
|
232
|
-
:
|
|
245
|
+
activeTab === "docs"
|
|
246
|
+
? "border-[#BA0C2F] text-[#BA0C2F]"
|
|
247
|
+
: "border-transparent text-gray-600 hover:text-black dark:text-gray-400 dark:hover:text-white"
|
|
233
248
|
}`}
|
|
234
249
|
>
|
|
235
250
|
Documentation
|
|
236
251
|
</button>
|
|
237
252
|
<button
|
|
238
|
-
onClick={() => setActiveTab(
|
|
253
|
+
onClick={() => setActiveTab("curl")}
|
|
239
254
|
className={`border-b-2 py-3 text-sm font-bold transition-colors ${
|
|
240
|
-
activeTab ===
|
|
241
|
-
?
|
|
242
|
-
:
|
|
255
|
+
activeTab === "curl"
|
|
256
|
+
? "border-[#BA0C2F] text-[#BA0C2F]"
|
|
257
|
+
: "border-transparent text-gray-600 hover:text-black dark:text-gray-400 dark:hover:text-white"
|
|
243
258
|
}`}
|
|
244
259
|
>
|
|
245
260
|
Curl Examples
|
|
@@ -249,7 +264,7 @@ export function APIModal({ id, type, data, onClose }: APIModalProps) {
|
|
|
249
264
|
|
|
250
265
|
{/* Content */}
|
|
251
266
|
<div className="flex-1 overflow-auto p-6">
|
|
252
|
-
{activeTab ===
|
|
267
|
+
{activeTab === "try-it" && (
|
|
253
268
|
<APITester
|
|
254
269
|
id={id}
|
|
255
270
|
endpoint={endpoint}
|
|
@@ -259,11 +274,13 @@ export function APIModal({ id, type, data, onClose }: APIModalProps) {
|
|
|
259
274
|
/>
|
|
260
275
|
)}
|
|
261
276
|
|
|
262
|
-
{activeTab ===
|
|
277
|
+
{activeTab === "docs" && (
|
|
263
278
|
<div className="space-y-6">
|
|
264
279
|
{/* Description */}
|
|
265
280
|
<div>
|
|
266
|
-
<h3 className="mb-2 text-lg font-bold text-black dark:text-white">
|
|
281
|
+
<h3 className="mb-2 text-lg font-bold text-black dark:text-white">
|
|
282
|
+
Description
|
|
283
|
+
</h3>
|
|
267
284
|
<p className="text-gray-600 dark:text-gray-400">
|
|
268
285
|
{data.description || `API endpoint for ${id}`}
|
|
269
286
|
</p>
|
|
@@ -271,46 +288,69 @@ export function APIModal({ id, type, data, onClose }: APIModalProps) {
|
|
|
271
288
|
|
|
272
289
|
{/* File Locations */}
|
|
273
290
|
<div>
|
|
274
|
-
<h3 className="mb-2 text-lg font-bold text-black dark:text-white">
|
|
291
|
+
<h3 className="mb-2 text-lg font-bold text-black dark:text-white">
|
|
292
|
+
File Locations
|
|
293
|
+
</h3>
|
|
275
294
|
<div className="space-y-2 border-2 border-black bg-gray-50 p-4 dark:border-gray-600 dark:bg-gray-800">
|
|
276
295
|
<div className="flex justify-between">
|
|
277
|
-
<span className="text-sm text-gray-600 dark:text-gray-400">
|
|
278
|
-
|
|
296
|
+
<span className="text-sm text-gray-600 dark:text-gray-400">
|
|
297
|
+
Route:
|
|
298
|
+
</span>
|
|
299
|
+
<code className="text-sm text-black dark:text-white">
|
|
300
|
+
{data.route || `src/app/api/v2/${id}/route.ts`}
|
|
301
|
+
</code>
|
|
279
302
|
</div>
|
|
280
303
|
{data.schemas && (
|
|
281
304
|
<div className="flex justify-between">
|
|
282
|
-
<span className="text-sm text-gray-600 dark:text-gray-400">
|
|
283
|
-
|
|
305
|
+
<span className="text-sm text-gray-600 dark:text-gray-400">
|
|
306
|
+
Schemas:
|
|
307
|
+
</span>
|
|
308
|
+
<code className="text-sm text-black dark:text-white">
|
|
309
|
+
{data.schemas}
|
|
310
|
+
</code>
|
|
284
311
|
</div>
|
|
285
312
|
)}
|
|
286
313
|
{data.tests && (
|
|
287
314
|
<div className="flex justify-between">
|
|
288
|
-
<span className="text-sm text-gray-600 dark:text-gray-400">
|
|
289
|
-
|
|
315
|
+
<span className="text-sm text-gray-600 dark:text-gray-400">
|
|
316
|
+
Tests:
|
|
317
|
+
</span>
|
|
318
|
+
<code className="text-sm text-black dark:text-white">
|
|
319
|
+
{data.tests}
|
|
320
|
+
</code>
|
|
290
321
|
</div>
|
|
291
322
|
)}
|
|
292
323
|
</div>
|
|
293
324
|
</div>
|
|
294
325
|
|
|
295
326
|
{/* Combined Info */}
|
|
296
|
-
{type ===
|
|
327
|
+
{type === "combined" && data.combines && (
|
|
297
328
|
<div>
|
|
298
|
-
<h3 className="mb-2 text-lg font-bold text-black dark:text-white">
|
|
329
|
+
<h3 className="mb-2 text-lg font-bold text-black dark:text-white">
|
|
330
|
+
Combined APIs
|
|
331
|
+
</h3>
|
|
299
332
|
<div className="border-2 border-black bg-gray-50 p-4 dark:border-gray-600 dark:bg-gray-800">
|
|
300
333
|
<p className="mb-2 text-sm text-gray-600 dark:text-gray-400">
|
|
301
334
|
This endpoint orchestrates the following APIs:
|
|
302
335
|
</p>
|
|
303
336
|
<ul className="list-inside list-disc space-y-1">
|
|
304
337
|
{data.combines.map((api) => (
|
|
305
|
-
<li
|
|
338
|
+
<li
|
|
339
|
+
key={api}
|
|
340
|
+
className="text-sm text-black dark:text-white"
|
|
341
|
+
>
|
|
306
342
|
{api}
|
|
307
343
|
</li>
|
|
308
344
|
))}
|
|
309
345
|
</ul>
|
|
310
346
|
{data.flow_type && (
|
|
311
347
|
<p className="mt-3 text-sm">
|
|
312
|
-
<span className="text-gray-600 dark:text-gray-400">
|
|
313
|
-
|
|
348
|
+
<span className="text-gray-600 dark:text-gray-400">
|
|
349
|
+
Flow type:
|
|
350
|
+
</span>{" "}
|
|
351
|
+
<span className="font-bold text-black dark:text-white">
|
|
352
|
+
{data.flow_type}
|
|
353
|
+
</span>
|
|
314
354
|
</p>
|
|
315
355
|
)}
|
|
316
356
|
</div>
|
|
@@ -319,17 +359,21 @@ export function APIModal({ id, type, data, onClose }: APIModalProps) {
|
|
|
319
359
|
</div>
|
|
320
360
|
)}
|
|
321
361
|
|
|
322
|
-
{activeTab ===
|
|
362
|
+
{activeTab === "curl" && (
|
|
323
363
|
<div className="space-y-4">
|
|
324
364
|
{methods.map((method) => (
|
|
325
365
|
<div key={method}>
|
|
326
|
-
<h3 className="mb-2 text-sm font-bold text-black dark:text-white">
|
|
366
|
+
<h3 className="mb-2 text-sm font-bold text-black dark:text-white">
|
|
367
|
+
{method} Request
|
|
368
|
+
</h3>
|
|
327
369
|
<div className="relative">
|
|
328
370
|
<pre className="overflow-x-auto border-2 border-black bg-zinc-900 p-4 text-sm text-zinc-100">
|
|
329
371
|
<code>{generateCurl(method)}</code>
|
|
330
372
|
</pre>
|
|
331
373
|
<button
|
|
332
|
-
onClick={() =>
|
|
374
|
+
onClick={() =>
|
|
375
|
+
navigator.clipboard.writeText(generateCurl(method))
|
|
376
|
+
}
|
|
333
377
|
className="absolute right-2 top-2 border border-zinc-600 bg-zinc-700 px-2 py-1 text-xs text-zinc-300 hover:bg-zinc-600"
|
|
334
378
|
>
|
|
335
379
|
Copy
|
|
@@ -355,10 +399,12 @@ export function APIModal({ id, type, data, onClose }: APIModalProps) {
|
|
|
355
399
|
)}
|
|
356
400
|
<button
|
|
357
401
|
onClick={() => {
|
|
358
|
-
const importPath = data.schemas
|
|
402
|
+
const importPath = data.schemas
|
|
403
|
+
?.replace(/^src\//, "@/")
|
|
404
|
+
.replace(/\.ts$/, "");
|
|
359
405
|
if (importPath) {
|
|
360
406
|
navigator.clipboard.writeText(
|
|
361
|
-
`import { RequestSchema, ResponseSchema } from '${importPath}'
|
|
407
|
+
`import { RequestSchema, ResponseSchema } from '${importPath}';`,
|
|
362
408
|
);
|
|
363
409
|
}
|
|
364
410
|
}}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
"use client";
|
|
2
2
|
|
|
3
|
-
import { useState, useMemo } from
|
|
4
|
-
import { APICard } from
|
|
5
|
-
import { APIModal } from
|
|
3
|
+
import { useState, useMemo } from "react";
|
|
4
|
+
import { APICard } from "./APICard";
|
|
5
|
+
import { APIModal } from "./APIModal";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Registry structure from .claude/registry.json
|
|
@@ -16,16 +16,22 @@ interface RegistryAPI {
|
|
|
16
16
|
methods?: string[];
|
|
17
17
|
created_at?: string;
|
|
18
18
|
status?: string;
|
|
19
|
-
endpoints?: Record<
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
endpoints?: Record<
|
|
20
|
+
string,
|
|
21
|
+
{
|
|
22
|
+
methods: string[];
|
|
23
|
+
description?: string;
|
|
24
|
+
}
|
|
25
|
+
>;
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
interface Registry {
|
|
26
29
|
version: string;
|
|
27
30
|
apis: Record<string, RegistryAPI>;
|
|
28
|
-
combined?: Record<
|
|
31
|
+
combined?: Record<
|
|
32
|
+
string,
|
|
33
|
+
RegistryAPI & { combines?: string[]; flow_type?: string }
|
|
34
|
+
>;
|
|
29
35
|
}
|
|
30
36
|
|
|
31
37
|
interface APIShowcaseProps {
|
|
@@ -45,31 +51,35 @@ interface APIShowcaseProps {
|
|
|
45
51
|
export function APIShowcase({ registry: propRegistry }: APIShowcaseProps) {
|
|
46
52
|
const [selectedAPI, setSelectedAPI] = useState<{
|
|
47
53
|
id: string;
|
|
48
|
-
type:
|
|
54
|
+
type: "api" | "combined";
|
|
49
55
|
data: RegistryAPI;
|
|
50
56
|
} | null>(null);
|
|
51
|
-
const [filter, setFilter] = useState<
|
|
52
|
-
const [search, setSearch] = useState(
|
|
57
|
+
const [filter, setFilter] = useState<"all" | "api" | "combined">("all");
|
|
58
|
+
const [search, setSearch] = useState("");
|
|
53
59
|
|
|
54
60
|
// Use prop registry or default empty structure
|
|
55
61
|
const registry: Registry = propRegistry || {
|
|
56
|
-
version:
|
|
62
|
+
version: "1.0.0",
|
|
57
63
|
apis: {},
|
|
58
64
|
combined: {},
|
|
59
65
|
};
|
|
60
66
|
|
|
61
67
|
// Combine APIs and combined endpoints into single list
|
|
62
68
|
const allAPIs = useMemo(() => {
|
|
63
|
-
const items: Array<{
|
|
69
|
+
const items: Array<{
|
|
70
|
+
id: string;
|
|
71
|
+
type: "api" | "combined";
|
|
72
|
+
data: RegistryAPI;
|
|
73
|
+
}> = [];
|
|
64
74
|
|
|
65
75
|
// Add regular APIs
|
|
66
76
|
Object.entries(registry.apis || {}).forEach(([id, data]) => {
|
|
67
|
-
items.push({ id, type:
|
|
77
|
+
items.push({ id, type: "api", data });
|
|
68
78
|
});
|
|
69
79
|
|
|
70
80
|
// Add combined APIs
|
|
71
81
|
Object.entries(registry.combined || {}).forEach(([id, data]) => {
|
|
72
|
-
items.push({ id, type:
|
|
82
|
+
items.push({ id, type: "combined", data });
|
|
73
83
|
});
|
|
74
84
|
|
|
75
85
|
return items;
|
|
@@ -79,7 +89,7 @@ export function APIShowcase({ registry: propRegistry }: APIShowcaseProps) {
|
|
|
79
89
|
const filteredAPIs = useMemo(() => {
|
|
80
90
|
return allAPIs.filter((item) => {
|
|
81
91
|
// Type filter
|
|
82
|
-
if (filter !==
|
|
92
|
+
if (filter !== "all" && item.type !== filter) {
|
|
83
93
|
return false;
|
|
84
94
|
}
|
|
85
95
|
|
|
@@ -111,16 +121,22 @@ export function APIShowcase({ registry: propRegistry }: APIShowcaseProps) {
|
|
|
111
121
|
{/* Stats Bar */}
|
|
112
122
|
<div className="mb-6 flex flex-wrap items-center gap-4 border-2 border-black bg-gray-50 p-4 dark:border-gray-600 dark:bg-gray-800">
|
|
113
123
|
<div className="flex items-center gap-2">
|
|
114
|
-
<span className="text-2xl font-bold text-black dark:text-white">
|
|
124
|
+
<span className="text-2xl font-bold text-black dark:text-white">
|
|
125
|
+
{stats.total}
|
|
126
|
+
</span>
|
|
115
127
|
<span className="text-gray-600 dark:text-gray-400">Total APIs</span>
|
|
116
128
|
</div>
|
|
117
129
|
<div className="h-8 w-px bg-black dark:bg-gray-600" />
|
|
118
130
|
<div className="flex items-center gap-2">
|
|
119
|
-
<span className="font-bold text-black dark:text-white">
|
|
131
|
+
<span className="font-bold text-black dark:text-white">
|
|
132
|
+
{stats.apis}
|
|
133
|
+
</span>
|
|
120
134
|
<span className="text-gray-600 dark:text-gray-400">Endpoints</span>
|
|
121
135
|
</div>
|
|
122
136
|
<div className="flex items-center gap-2">
|
|
123
|
-
<span className="font-bold text-black dark:text-white">
|
|
137
|
+
<span className="font-bold text-black dark:text-white">
|
|
138
|
+
{stats.combined}
|
|
139
|
+
</span>
|
|
124
140
|
<span className="text-gray-600 dark:text-gray-400">Combined</span>
|
|
125
141
|
</div>
|
|
126
142
|
</div>
|
|
@@ -141,31 +157,31 @@ export function APIShowcase({ registry: propRegistry }: APIShowcaseProps) {
|
|
|
141
157
|
{/* Type Filter */}
|
|
142
158
|
<div className="flex gap-2">
|
|
143
159
|
<button
|
|
144
|
-
onClick={() => setFilter(
|
|
160
|
+
onClick={() => setFilter("all")}
|
|
145
161
|
className={`border-2 px-4 py-2 text-sm font-bold transition-colors ${
|
|
146
|
-
filter ===
|
|
147
|
-
?
|
|
148
|
-
:
|
|
162
|
+
filter === "all"
|
|
163
|
+
? "border-[#BA0C2F] bg-[#BA0C2F] text-white"
|
|
164
|
+
: "border-black bg-white text-black hover:border-[#BA0C2F] dark:border-gray-600 dark:bg-gray-800 dark:text-white"
|
|
149
165
|
}`}
|
|
150
166
|
>
|
|
151
167
|
All ({stats.total})
|
|
152
168
|
</button>
|
|
153
169
|
<button
|
|
154
|
-
onClick={() => setFilter(
|
|
170
|
+
onClick={() => setFilter("api")}
|
|
155
171
|
className={`border-2 px-4 py-2 text-sm font-bold transition-colors ${
|
|
156
|
-
filter ===
|
|
157
|
-
?
|
|
158
|
-
:
|
|
172
|
+
filter === "api"
|
|
173
|
+
? "border-[#BA0C2F] bg-[#BA0C2F] text-white"
|
|
174
|
+
: "border-black bg-white text-black hover:border-[#BA0C2F] dark:border-gray-600 dark:bg-gray-800 dark:text-white"
|
|
159
175
|
}`}
|
|
160
176
|
>
|
|
161
177
|
APIs ({stats.apis})
|
|
162
178
|
</button>
|
|
163
179
|
<button
|
|
164
|
-
onClick={() => setFilter(
|
|
180
|
+
onClick={() => setFilter("combined")}
|
|
165
181
|
className={`border-2 px-4 py-2 text-sm font-bold transition-colors ${
|
|
166
|
-
filter ===
|
|
167
|
-
?
|
|
168
|
-
:
|
|
182
|
+
filter === "combined"
|
|
183
|
+
? "border-[#BA0C2F] bg-[#BA0C2F] text-white"
|
|
184
|
+
: "border-black bg-white text-black hover:border-[#BA0C2F] dark:border-gray-600 dark:bg-gray-800 dark:text-white"
|
|
169
185
|
}`}
|
|
170
186
|
>
|
|
171
187
|
Combined ({stats.combined})
|
|
@@ -208,11 +224,13 @@ export function APIShowcase({ registry: propRegistry }: APIShowcaseProps) {
|
|
|
208
224
|
<circle cx="12" cy="12" r="10" />
|
|
209
225
|
</svg>
|
|
210
226
|
</div>
|
|
211
|
-
<p className="text-lg font-bold text-black dark:text-white">
|
|
227
|
+
<p className="text-lg font-bold text-black dark:text-white">
|
|
228
|
+
No APIs found
|
|
229
|
+
</p>
|
|
212
230
|
<p className="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
|
213
231
|
{search
|
|
214
|
-
?
|
|
215
|
-
:
|
|
232
|
+
? "Try a different search term"
|
|
233
|
+
: "Create your first API with /api-create"}
|
|
216
234
|
</p>
|
|
217
235
|
</div>
|
|
218
236
|
)}
|