@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.
Files changed (139) hide show
  1. package/.claude/agents/code-reviewer.md +170 -0
  2. package/.claude/agents/docs-generator.md +80 -0
  3. package/.claude/agents/implementation-reviewer.md +119 -0
  4. package/.claude/agents/parallel-researcher.md +52 -0
  5. package/.claude/agents/research-validator.md +116 -0
  6. package/.claude/agents/schema-generator.md +70 -0
  7. package/.claude/agents/test-writer.md +104 -0
  8. package/.claude/api-dev-state.json +305 -56
  9. package/.claude/commands/README.md +21 -10
  10. package/.claude/commands/add-command.md +8 -5
  11. package/.claude/commands/api-create.md +36 -25
  12. package/.claude/commands/api-env.md +1 -0
  13. package/.claude/commands/api-interview.md +32 -19
  14. package/.claude/commands/api-research.md +47 -21
  15. package/.claude/commands/api-status.md +21 -1
  16. package/.claude/commands/api-verify.md +14 -13
  17. package/.claude/commands/beepboop.md +4 -5
  18. package/.claude/commands/busycommit.md +2 -3
  19. package/.claude/commands/commit.md +2 -3
  20. package/.claude/commands/cycle.md +2 -7
  21. package/.claude/commands/gap.md +2 -3
  22. package/.claude/commands/green.md +2 -7
  23. package/.claude/commands/issue.md +3 -8
  24. package/.claude/commands/ntfy-setup.md +91 -0
  25. package/.claude/commands/ntfy-test.md +74 -0
  26. package/.claude/commands/plan.md +2 -3
  27. package/.claude/commands/pr.md +2 -3
  28. package/.claude/commands/publish.md +40 -0
  29. package/.claude/commands/red.md +2 -7
  30. package/.claude/commands/refactor.md +2 -7
  31. package/.claude/commands/spike.md +2 -7
  32. package/.claude/commands/summarize.md +2 -3
  33. package/.claude/commands/tdd.md +2 -7
  34. package/.claude/commands/worktree-add.md +208 -216
  35. package/.claude/commands/worktree-cleanup.md +172 -178
  36. package/.claude/settings.json +63 -12
  37. package/.claude/settings.local.json +2 -1
  38. package/.claude-plugin/marketplace.json +2 -11
  39. package/.skills/README.md +55 -53
  40. package/.skills/_shared/settings.json +1 -1
  41. package/.skills/add-command/SKILL.md +10 -5
  42. package/.skills/api-create/SKILL.md +146 -35
  43. package/.skills/api-env/SKILL.md +1 -0
  44. package/.skills/api-interview/SKILL.md +32 -19
  45. package/.skills/api-research/SKILL.md +47 -21
  46. package/.skills/api-status/SKILL.md +21 -1
  47. package/.skills/api-verify/SKILL.md +14 -13
  48. package/.skills/beepboop/SKILL.md +6 -5
  49. package/.skills/busycommit/SKILL.md +4 -3
  50. package/.skills/commit/SKILL.md +4 -3
  51. package/.skills/cycle/SKILL.md +4 -7
  52. package/.skills/gap/SKILL.md +4 -3
  53. package/.skills/green/SKILL.md +4 -7
  54. package/.skills/issue/SKILL.md +5 -8
  55. package/.skills/plan/SKILL.md +4 -3
  56. package/.skills/pr/SKILL.md +4 -3
  57. package/.skills/publish/SKILL.md +160 -0
  58. package/.skills/red/SKILL.md +4 -7
  59. package/.skills/refactor/SKILL.md +4 -7
  60. package/.skills/spike/SKILL.md +4 -7
  61. package/.skills/summarize/SKILL.md +4 -3
  62. package/.skills/tdd/SKILL.md +4 -7
  63. package/.skills/update-todos/SKILL.md +22 -0
  64. package/.skills/worktree-add/SKILL.md +210 -216
  65. package/.skills/worktree-cleanup/SKILL.md +183 -187
  66. package/CHANGELOG.md +97 -79
  67. package/README.md +161 -7142
  68. package/bin/cli.js +448 -805
  69. package/commands/README.md +66 -31
  70. package/commands/add-command.md +8 -5
  71. package/commands/beepboop.md +4 -5
  72. package/commands/busycommit.md +2 -3
  73. package/commands/commit.md +2 -3
  74. package/commands/cycle.md +2 -7
  75. package/commands/gap.md +2 -3
  76. package/commands/green.md +2 -7
  77. package/commands/hustle-api-continue.md +8 -5
  78. package/commands/hustle-api-create.md +70 -29
  79. package/commands/hustle-api-env.md +1 -0
  80. package/commands/hustle-api-interview.md +32 -19
  81. package/commands/hustle-api-research.md +47 -21
  82. package/commands/hustle-api-sessions.md +8 -7
  83. package/commands/hustle-api-status.md +21 -1
  84. package/commands/hustle-api-verify.md +14 -13
  85. package/commands/hustle-combine.md +488 -241
  86. package/commands/hustle-ui-create-page.md +113 -50
  87. package/commands/hustle-ui-create.md +179 -26
  88. package/commands/issue.md +3 -8
  89. package/commands/plan.md +2 -3
  90. package/commands/pr.md +2 -3
  91. package/commands/red.md +2 -7
  92. package/commands/refactor.md +2 -7
  93. package/commands/spike.md +2 -7
  94. package/commands/summarize.md +2 -3
  95. package/commands/tdd.md +2 -7
  96. package/commands/worktree-add.md +208 -216
  97. package/commands/worktree-cleanup.md +172 -178
  98. package/hooks/api-workflow-check.py +5 -3
  99. package/hooks/enforce-component-type-confirm.py +97 -0
  100. package/hooks/lib/__init__.py +1 -0
  101. package/hooks/lib/greptile.py +355 -0
  102. package/hooks/lib/ntfy.py +209 -0
  103. package/hooks/notify-input-needed.py +73 -0
  104. package/hooks/notify-phase-complete.py +90 -0
  105. package/hooks/run-code-review.py +246 -0
  106. package/hooks/track-token-usage.py +121 -0
  107. package/package.json +13 -3
  108. package/scripts/collect-test-results.ts +102 -77
  109. package/scripts/extract-parameters.ts +112 -70
  110. package/scripts/generate-test-manifest.ts +118 -77
  111. package/templates/.env.example +57 -0
  112. package/templates/BRAND_GUIDE.md +92 -52
  113. package/templates/CLAUDE-SECTION.md +40 -37
  114. package/templates/SPEC.json +186 -38
  115. package/templates/api-dev-state.json +33 -4
  116. package/templates/api-showcase/_components/APICard.tsx +22 -18
  117. package/templates/api-showcase/_components/APIModal.tsx +110 -64
  118. package/templates/api-showcase/_components/APIShowcase.tsx +53 -35
  119. package/templates/api-showcase/_components/APITester.tsx +128 -67
  120. package/templates/api-showcase/page.tsx +4 -4
  121. package/templates/api-test/page.tsx +51 -30
  122. package/templates/api-test/test-structure/route.ts +43 -34
  123. package/templates/component/Component.stories.tsx +41 -39
  124. package/templates/component/Component.test.tsx +96 -78
  125. package/templates/component/Component.tsx +63 -52
  126. package/templates/component/Component.types.ts +10 -6
  127. package/templates/component/Component.visual.spec.ts +170 -0
  128. package/templates/component/index.ts +2 -2
  129. package/templates/dev-tools/_components/DevToolsLanding.tsx +8 -8
  130. package/templates/dev-tools/page.tsx +4 -3
  131. package/templates/mcp-servers.json +30 -2
  132. package/templates/page/page.e2e.test.ts +56 -48
  133. package/templates/page/page.tsx +3 -3
  134. package/templates/shared/HeroHeader.tsx +16 -15
  135. package/templates/shared/index.ts +1 -1
  136. package/templates/ui-showcase/_components/PreviewCard.tsx +20 -20
  137. package/templates/ui-showcase/_components/PreviewModal.tsx +149 -108
  138. package/templates/ui-showcase/_components/UIShowcase.tsx +43 -35
  139. package/templates/ui-showcase/page.tsx +4 -4
@@ -1,7 +1,7 @@
1
- 'use client';
1
+ "use client";
2
2
 
3
- import { useEffect, useCallback, useState } from 'react';
4
- import { APITester } from './APITester';
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<string, {
18
- methods: string[];
19
- description?: string;
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: 'api' | 'combined';
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<'try-it' | 'docs' | 'curl'>('try-it');
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 === 'Escape') {
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('keydown', handleKeyDown);
59
- document.body.style.overflow = 'hidden';
63
+ document.addEventListener("keydown", handleKeyDown);
64
+ document.body.style.overflow = "hidden";
60
65
 
61
66
  return () => {
62
- document.removeEventListener('keydown', handleKeyDown);
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 || { default: { methods: data.methods || ['POST'] } };
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 ? endpoints[selectedEndpoint] : endpoints[endpointKeys[0]];
80
- const methods = currentEndpoint?.methods || data.methods || ['POST'];
81
- const baseUrl = typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000';
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 !== 'default') {
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 === 'GET') {
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 id="modal-title" className="text-xl font-bold text-black dark:text-white">
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 === 'combined' && (
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 === 'GET'
149
- ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200'
150
- : method === 'POST'
151
- ? 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200'
152
- : method === 'DELETE'
153
- ? 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'
154
- : 'bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200'
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
- ? 'border-[#BA0C2F] bg-[#BA0C2F] text-white'
199
- : 'border-black bg-white text-black hover:border-[#BA0C2F] dark:border-gray-600 dark:bg-gray-700 dark:text-white'
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('try-it')}
233
+ onClick={() => setActiveTab("try-it")}
219
234
  className={`border-b-2 py-3 text-sm font-bold transition-colors ${
220
- activeTab === 'try-it'
221
- ? 'border-[#BA0C2F] text-[#BA0C2F]'
222
- : 'border-transparent text-gray-600 hover:text-black dark:text-gray-400 dark:hover:text-white'
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('docs')}
243
+ onClick={() => setActiveTab("docs")}
229
244
  className={`border-b-2 py-3 text-sm font-bold transition-colors ${
230
- activeTab === 'docs'
231
- ? 'border-[#BA0C2F] text-[#BA0C2F]'
232
- : 'border-transparent text-gray-600 hover:text-black dark:text-gray-400 dark:hover:text-white'
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('curl')}
253
+ onClick={() => setActiveTab("curl")}
239
254
  className={`border-b-2 py-3 text-sm font-bold transition-colors ${
240
- activeTab === 'curl'
241
- ? 'border-[#BA0C2F] text-[#BA0C2F]'
242
- : 'border-transparent text-gray-600 hover:text-black dark:text-gray-400 dark:hover:text-white'
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 === 'try-it' && (
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 === 'docs' && (
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">Description</h3>
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">File Locations</h3>
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">Route:</span>
278
- <code className="text-sm text-black dark:text-white">{data.route || `src/app/api/v2/${id}/route.ts`}</code>
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">Schemas:</span>
283
- <code className="text-sm text-black dark:text-white">{data.schemas}</code>
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">Tests:</span>
289
- <code className="text-sm text-black dark:text-white">{data.tests}</code>
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 === 'combined' && data.combines && (
327
+ {type === "combined" && data.combines && (
297
328
  <div>
298
- <h3 className="mb-2 text-lg font-bold text-black dark:text-white">Combined APIs</h3>
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 key={api} className="text-sm text-black dark:text-white">
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">Flow type:</span>{' '}
313
- <span className="font-bold text-black dark:text-white">{data.flow_type}</span>
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 === 'curl' && (
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">{method} Request</h3>
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={() => navigator.clipboard.writeText(generateCurl(method))}
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?.replace(/^src\//, '@/').replace(/\.ts$/, '');
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
- 'use client';
1
+ "use client";
2
2
 
3
- import { useState, useMemo } from 'react';
4
- import { APICard } from './APICard';
5
- import { APIModal } from './APIModal';
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<string, {
20
- methods: string[];
21
- description?: string;
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<string, RegistryAPI & { combines?: string[]; flow_type?: string }>;
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: 'api' | 'combined';
54
+ type: "api" | "combined";
49
55
  data: RegistryAPI;
50
56
  } | null>(null);
51
- const [filter, setFilter] = useState<'all' | 'api' | 'combined'>('all');
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: '1.0.0',
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<{ id: string; type: 'api' | 'combined'; data: RegistryAPI }> = [];
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: 'api', data });
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: 'combined', data });
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 !== 'all' && item.type !== 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">{stats.total}</span>
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">{stats.apis}</span>
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">{stats.combined}</span>
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('all')}
160
+ onClick={() => setFilter("all")}
145
161
  className={`border-2 px-4 py-2 text-sm font-bold transition-colors ${
146
- filter === 'all'
147
- ? 'border-[#BA0C2F] bg-[#BA0C2F] text-white'
148
- : 'border-black bg-white text-black hover:border-[#BA0C2F] dark:border-gray-600 dark:bg-gray-800 dark:text-white'
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('api')}
170
+ onClick={() => setFilter("api")}
155
171
  className={`border-2 px-4 py-2 text-sm font-bold transition-colors ${
156
- filter === 'api'
157
- ? 'border-[#BA0C2F] bg-[#BA0C2F] text-white'
158
- : 'border-black bg-white text-black hover:border-[#BA0C2F] dark:border-gray-600 dark:bg-gray-800 dark:text-white'
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('combined')}
180
+ onClick={() => setFilter("combined")}
165
181
  className={`border-2 px-4 py-2 text-sm font-bold transition-colors ${
166
- filter === 'combined'
167
- ? 'border-[#BA0C2F] bg-[#BA0C2F] text-white'
168
- : 'border-black bg-white text-black hover:border-[#BA0C2F] dark:border-gray-600 dark:bg-gray-800 dark:text-white'
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">No APIs found</p>
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
- ? 'Try a different search term'
215
- : 'Create your first API with /api-create'}
232
+ ? "Try a different search term"
233
+ : "Create your first API with /api-create"}
216
234
  </p>
217
235
  </div>
218
236
  )}