@hustle-together/api-dev-tools 3.9.2 → 3.10.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.
- package/README.md +311 -19
- package/bin/cli.js +54 -7
- package/commands/hustle-api-create.md +13 -13
- package/commands/hustle-ui-create-page.md +933 -0
- package/hooks/api-workflow-check.py +160 -2
- package/hooks/check-api-routes.py +168 -0
- package/hooks/enforce-a11y-audit.py +202 -0
- package/hooks/enforce-brand-guide.py +115 -5
- package/hooks/enforce-page-components.py +186 -0
- package/hooks/enforce-page-data-schema.py +155 -0
- package/hooks/generate-manifest-entry.py +181 -1
- package/hooks/session-startup.py +95 -5
- package/hooks/update-ui-showcase.py +67 -3
- package/package.json +1 -2
- package/templates/api-dev-state.json +39 -1
- package/templates/settings.json +16 -0
- package/templates/shared/HeroHeader.tsx +1 -1
- package/demo/hustle-together/blog/gemini-vs-claude-widgets.html +0 -959
- package/demo/hustle-together/blog/interview-driven-api-development.html +0 -1146
- package/demo/hustle-together/blog/tdd-for-ai.html +0 -982
- package/demo/hustle-together/index.html +0 -1312
- package/demo/workflow-demo-v3.5-backup.html +0 -5008
- package/demo/workflow-demo.html +0 -6202
- /package/templates/api-showcase/{APICard.tsx → _components/APICard.tsx} +0 -0
- /package/templates/api-showcase/{APIModal.tsx → _components/APIModal.tsx} +0 -0
- /package/templates/api-showcase/{APIShowcase.tsx → _components/APIShowcase.tsx} +0 -0
- /package/templates/api-showcase/{APITester.tsx → _components/APITester.tsx} +0 -0
- /package/templates/ui-showcase/{PreviewCard.tsx → _components/PreviewCard.tsx} +0 -0
- /package/templates/ui-showcase/{PreviewModal.tsx → _components/PreviewModal.tsx} +0 -0
- /package/templates/ui-showcase/{UIShowcase.tsx → _components/UIShowcase.tsx} +0 -0
|
@@ -787,8 +787,169 @@ def generate_test_cases(schema: dict) -> list:
|
|
|
787
787
|
return test_cases
|
|
788
788
|
|
|
789
789
|
|
|
790
|
+
def generate_orchestration_examples(endpoint: str, combine_config: dict, method: str) -> list:
|
|
791
|
+
"""Generate orchestration examples for combined API endpoints.
|
|
792
|
+
|
|
793
|
+
Shows how multiple APIs are called in sequence/parallel and how data flows.
|
|
794
|
+
"""
|
|
795
|
+
examples = []
|
|
796
|
+
source_elements = combine_config.get("source_elements", [])
|
|
797
|
+
flow_type = combine_config.get("flow_type", "sequential")
|
|
798
|
+
error_strategy = combine_config.get("error_strategy", "fail-fast")
|
|
799
|
+
|
|
800
|
+
if len(source_elements) < 2:
|
|
801
|
+
return examples
|
|
802
|
+
|
|
803
|
+
base_url = f"http://localhost:3001/api/v2/{endpoint}"
|
|
804
|
+
|
|
805
|
+
# Get source API names
|
|
806
|
+
source_names = []
|
|
807
|
+
for elem in source_elements:
|
|
808
|
+
if isinstance(elem, dict):
|
|
809
|
+
source_names.append(elem.get("name", "unknown"))
|
|
810
|
+
else:
|
|
811
|
+
source_names.append(str(elem))
|
|
812
|
+
|
|
813
|
+
# ================================================================
|
|
814
|
+
# Example 1: Sequential flow
|
|
815
|
+
# ================================================================
|
|
816
|
+
if flow_type == "sequential":
|
|
817
|
+
seq_body = {
|
|
818
|
+
"steps": [
|
|
819
|
+
{"api": source_names[0], "params": {"input": "example"}},
|
|
820
|
+
{"api": source_names[1], "params": {"useResultFrom": source_names[0]}}
|
|
821
|
+
]
|
|
822
|
+
}
|
|
823
|
+
curl = f'curl -X {method} {base_url} \\\n'
|
|
824
|
+
curl += ' -H "Content-Type: application/json" \\\n'
|
|
825
|
+
curl += f" -d '{json.dumps(seq_body, indent=2)}'"
|
|
826
|
+
|
|
827
|
+
examples.append({
|
|
828
|
+
"name": "Sequential orchestration",
|
|
829
|
+
"description": f"Calls {source_names[0]} first, then {source_names[1]} with results",
|
|
830
|
+
"request": seq_body,
|
|
831
|
+
"curl": curl,
|
|
832
|
+
"flow": f"{source_names[0]} → {source_names[1]}"
|
|
833
|
+
})
|
|
834
|
+
|
|
835
|
+
# ================================================================
|
|
836
|
+
# Example 2: Parallel flow
|
|
837
|
+
# ================================================================
|
|
838
|
+
elif flow_type == "parallel":
|
|
839
|
+
par_body = {
|
|
840
|
+
"parallel": True,
|
|
841
|
+
"apis": [
|
|
842
|
+
{"name": source_names[0], "params": {"query": "example1"}},
|
|
843
|
+
{"name": source_names[1], "params": {"query": "example2"}}
|
|
844
|
+
],
|
|
845
|
+
"merge_strategy": "combine"
|
|
846
|
+
}
|
|
847
|
+
curl = f'curl -X {method} {base_url} \\\n'
|
|
848
|
+
curl += ' -H "Content-Type: application/json" \\\n'
|
|
849
|
+
curl += f" -d '{json.dumps(par_body, indent=2)}'"
|
|
850
|
+
|
|
851
|
+
examples.append({
|
|
852
|
+
"name": "Parallel orchestration",
|
|
853
|
+
"description": f"Calls {source_names[0]} and {source_names[1]} simultaneously",
|
|
854
|
+
"request": par_body,
|
|
855
|
+
"curl": curl,
|
|
856
|
+
"flow": f"[{source_names[0]} || {source_names[1]}] → merge"
|
|
857
|
+
})
|
|
858
|
+
|
|
859
|
+
# ================================================================
|
|
860
|
+
# Example 3: Conditional flow
|
|
861
|
+
# ================================================================
|
|
862
|
+
elif flow_type == "conditional":
|
|
863
|
+
cond_body = {
|
|
864
|
+
"condition": {
|
|
865
|
+
"check": source_names[0],
|
|
866
|
+
"if_success": source_names[1],
|
|
867
|
+
"if_failure": "fallback"
|
|
868
|
+
},
|
|
869
|
+
"params": {"input": "example"}
|
|
870
|
+
}
|
|
871
|
+
curl = f'curl -X {method} {base_url} \\\n'
|
|
872
|
+
curl += ' -H "Content-Type: application/json" \\\n'
|
|
873
|
+
curl += f" -d '{json.dumps(cond_body, indent=2)}'"
|
|
874
|
+
|
|
875
|
+
examples.append({
|
|
876
|
+
"name": "Conditional orchestration",
|
|
877
|
+
"description": f"Calls {source_names[1]} only if {source_names[0]} succeeds",
|
|
878
|
+
"request": cond_body,
|
|
879
|
+
"curl": curl,
|
|
880
|
+
"flow": f"{source_names[0]} ? {source_names[1]} : fallback"
|
|
881
|
+
})
|
|
882
|
+
|
|
883
|
+
# ================================================================
|
|
884
|
+
# Example 4: With error handling
|
|
885
|
+
# ================================================================
|
|
886
|
+
error_body = {
|
|
887
|
+
"steps": [
|
|
888
|
+
{"api": source_names[0], "params": {"input": "data"}},
|
|
889
|
+
{"api": source_names[1], "params": {"input": "data"}}
|
|
890
|
+
],
|
|
891
|
+
"error_strategy": error_strategy,
|
|
892
|
+
"retry": {"attempts": 3, "delay_ms": 1000}
|
|
893
|
+
}
|
|
894
|
+
curl = f'curl -X {method} {base_url} \\\n'
|
|
895
|
+
curl += ' -H "Content-Type: application/json" \\\n'
|
|
896
|
+
curl += f" -d '{json.dumps(error_body, indent=2)}'"
|
|
897
|
+
|
|
898
|
+
examples.append({
|
|
899
|
+
"name": f"With {error_strategy} error handling",
|
|
900
|
+
"description": f"Orchestration with {error_strategy} strategy and retry logic",
|
|
901
|
+
"request": error_body,
|
|
902
|
+
"curl": curl
|
|
903
|
+
})
|
|
904
|
+
|
|
905
|
+
# ================================================================
|
|
906
|
+
# Example 5: Full data flow
|
|
907
|
+
# ================================================================
|
|
908
|
+
full_body = {
|
|
909
|
+
"input": {
|
|
910
|
+
"source": "user-provided",
|
|
911
|
+
"data": {"query": "example query", "options": {"format": "json"}}
|
|
912
|
+
},
|
|
913
|
+
"flow": [
|
|
914
|
+
{
|
|
915
|
+
"step": 1,
|
|
916
|
+
"api": source_names[0],
|
|
917
|
+
"transform": "extract.content"
|
|
918
|
+
},
|
|
919
|
+
{
|
|
920
|
+
"step": 2,
|
|
921
|
+
"api": source_names[1],
|
|
922
|
+
"input_mapping": {"content": "$.step1.result"},
|
|
923
|
+
"transform": "format.output"
|
|
924
|
+
}
|
|
925
|
+
],
|
|
926
|
+
"output": {"include": ["final_result", "metadata"]}
|
|
927
|
+
}
|
|
928
|
+
curl = f'curl -X {method} {base_url} \\\n'
|
|
929
|
+
curl += ' -H "Content-Type: application/json" \\\n'
|
|
930
|
+
curl += f" -d '{json.dumps(full_body, indent=2)}'"
|
|
931
|
+
|
|
932
|
+
examples.append({
|
|
933
|
+
"name": "Complete orchestration with transforms",
|
|
934
|
+
"description": "Full data flow with input mapping and output transformation",
|
|
935
|
+
"request": full_body,
|
|
936
|
+
"curl": curl,
|
|
937
|
+
"flow_diagram": f"""
|
|
938
|
+
Input → [{source_names[0]}] → transform → [{source_names[1]}] → Output
|
|
939
|
+
↑ ↑
|
|
940
|
+
extract.content format.output
|
|
941
|
+
"""
|
|
942
|
+
})
|
|
943
|
+
|
|
944
|
+
return examples
|
|
945
|
+
|
|
946
|
+
|
|
790
947
|
def generate_manifest_entry(endpoint: str, endpoint_data: dict, state: dict) -> dict:
|
|
791
948
|
"""Generate a complete manifest entry for the endpoint."""
|
|
949
|
+
# Check if this is a combined workflow
|
|
950
|
+
combine_config = state.get("combine_config", {})
|
|
951
|
+
is_combined = bool(combine_config.get("source_elements"))
|
|
952
|
+
|
|
792
953
|
# Find schema file
|
|
793
954
|
schema_file = endpoint_data.get("phases", {}).get("schema_creation", {}).get("schema_file")
|
|
794
955
|
if not schema_file:
|
|
@@ -819,6 +980,11 @@ def generate_manifest_entry(endpoint: str, endpoint_data: dict, state: dict) ->
|
|
|
819
980
|
# Generate examples
|
|
820
981
|
examples = generate_curl_examples(endpoint, method, request_schema)
|
|
821
982
|
|
|
983
|
+
# Add orchestration examples for combined endpoints
|
|
984
|
+
if is_combined:
|
|
985
|
+
orchestration_examples = generate_orchestration_examples(endpoint, combine_config, method)
|
|
986
|
+
examples.extend(orchestration_examples)
|
|
987
|
+
|
|
822
988
|
# Generate test cases
|
|
823
989
|
test_cases = generate_test_cases(request_schema)
|
|
824
990
|
|
|
@@ -853,12 +1019,26 @@ def generate_manifest_entry(endpoint: str, endpoint_data: dict, state: dict) ->
|
|
|
853
1019
|
"testCases": test_cases,
|
|
854
1020
|
"metadata": {
|
|
855
1021
|
"generatedAt": datetime.now().isoformat(),
|
|
856
|
-
"generatedBy": "api-dev-tools v3.
|
|
1022
|
+
"generatedBy": "api-dev-tools v3.10.0",
|
|
857
1023
|
"schemaFile": schema_file,
|
|
858
1024
|
"researchFresh": True
|
|
859
1025
|
}
|
|
860
1026
|
}
|
|
861
1027
|
|
|
1028
|
+
# Add combined workflow metadata
|
|
1029
|
+
if is_combined:
|
|
1030
|
+
source_names = []
|
|
1031
|
+
for elem in combine_config.get("source_elements", []):
|
|
1032
|
+
if isinstance(elem, dict):
|
|
1033
|
+
source_names.append(elem.get("name", "unknown"))
|
|
1034
|
+
else:
|
|
1035
|
+
source_names.append(str(elem))
|
|
1036
|
+
|
|
1037
|
+
entry["metadata"]["isCombined"] = True
|
|
1038
|
+
entry["metadata"]["sourceApis"] = source_names
|
|
1039
|
+
entry["metadata"]["flowType"] = combine_config.get("flow_type", "sequential")
|
|
1040
|
+
entry["metadata"]["errorStrategy"] = combine_config.get("error_strategy", "fail-fast")
|
|
1041
|
+
|
|
862
1042
|
return entry
|
|
863
1043
|
|
|
864
1044
|
|
package/hooks/session-startup.py
CHANGED
|
@@ -30,6 +30,22 @@ STATE_FILE = Path(__file__).parent.parent / "api-dev-state.json"
|
|
|
30
30
|
RESEARCH_INDEX = Path(__file__).parent.parent / "research" / "index.json"
|
|
31
31
|
|
|
32
32
|
|
|
33
|
+
def get_workflow_type(state):
|
|
34
|
+
"""Detect the workflow type from state."""
|
|
35
|
+
workflow = state.get("workflow", "")
|
|
36
|
+
if workflow:
|
|
37
|
+
return workflow
|
|
38
|
+
|
|
39
|
+
# Infer from state structure
|
|
40
|
+
if state.get("combine_config"):
|
|
41
|
+
return "combine-api"
|
|
42
|
+
if state.get("ui_config"):
|
|
43
|
+
mode = state.get("ui_config", {}).get("mode", "")
|
|
44
|
+
return f"ui-create-{mode}" if mode else "ui-create-component"
|
|
45
|
+
|
|
46
|
+
return "api-create"
|
|
47
|
+
|
|
48
|
+
|
|
33
49
|
def get_active_endpoint(state):
|
|
34
50
|
"""Get active endpoint - supports both old and new state formats."""
|
|
35
51
|
# New format (v3.6.7+): endpoints object with active_endpoint pointer
|
|
@@ -39,12 +55,24 @@ def get_active_endpoint(state):
|
|
|
39
55
|
return active, state["endpoints"][active]
|
|
40
56
|
return None, None
|
|
41
57
|
|
|
58
|
+
# Support for elements (UI workflow)
|
|
59
|
+
if "elements" in state and "active_element" in state:
|
|
60
|
+
active = state.get("active_element")
|
|
61
|
+
if active and active in state["elements"]:
|
|
62
|
+
return active, state["elements"][active]
|
|
63
|
+
return None, None
|
|
64
|
+
|
|
42
65
|
# Old format: single endpoint field
|
|
43
66
|
endpoint = state.get("endpoint")
|
|
44
67
|
if endpoint:
|
|
45
68
|
# Return endpoint name and the entire state as endpoint data
|
|
46
69
|
return endpoint, state
|
|
47
70
|
|
|
71
|
+
# Try active_element without elements dict
|
|
72
|
+
active = state.get("active_element")
|
|
73
|
+
if active:
|
|
74
|
+
return active, state
|
|
75
|
+
|
|
48
76
|
return None, None
|
|
49
77
|
|
|
50
78
|
|
|
@@ -99,11 +127,64 @@ def main():
|
|
|
99
127
|
print(json.dumps({"continue": True}))
|
|
100
128
|
sys.exit(0)
|
|
101
129
|
|
|
130
|
+
# Detect workflow type
|
|
131
|
+
workflow_type = get_workflow_type(state)
|
|
132
|
+
|
|
102
133
|
# Build context summary
|
|
103
134
|
context_parts = []
|
|
104
|
-
|
|
135
|
+
|
|
136
|
+
# Header based on workflow type
|
|
137
|
+
if workflow_type == "combine-api":
|
|
138
|
+
context_parts.append("## Combined API Development Session Context")
|
|
139
|
+
elif workflow_type.startswith("ui-create"):
|
|
140
|
+
mode = "Page" if "page" in workflow_type else "Component"
|
|
141
|
+
context_parts.append(f"## UI {mode} Development Session Context")
|
|
142
|
+
else:
|
|
143
|
+
context_parts.append("## API Development Session Context")
|
|
144
|
+
|
|
105
145
|
context_parts.append("")
|
|
106
|
-
context_parts.append(f"**
|
|
146
|
+
context_parts.append(f"**Workflow:** {workflow_type}")
|
|
147
|
+
context_parts.append(f"**Active Element:** {endpoint}")
|
|
148
|
+
|
|
149
|
+
# Add combine-specific context
|
|
150
|
+
if workflow_type == "combine-api":
|
|
151
|
+
combine_config = state.get("combine_config", {})
|
|
152
|
+
source_elements = combine_config.get("source_elements", [])
|
|
153
|
+
flow_type = combine_config.get("flow_type", "sequential")
|
|
154
|
+
error_strategy = combine_config.get("error_strategy", "fail-fast")
|
|
155
|
+
|
|
156
|
+
if source_elements:
|
|
157
|
+
source_names = []
|
|
158
|
+
for elem in source_elements:
|
|
159
|
+
if isinstance(elem, dict):
|
|
160
|
+
source_names.append(elem.get("name", "unknown"))
|
|
161
|
+
else:
|
|
162
|
+
source_names.append(str(elem))
|
|
163
|
+
|
|
164
|
+
context_parts.append("")
|
|
165
|
+
context_parts.append("**Combining APIs:**")
|
|
166
|
+
for name in source_names:
|
|
167
|
+
context_parts.append(f" - {name}")
|
|
168
|
+
context_parts.append(f" Flow: {flow_type}")
|
|
169
|
+
context_parts.append(f" Error Strategy: {error_strategy}")
|
|
170
|
+
|
|
171
|
+
# Add UI-specific context
|
|
172
|
+
elif workflow_type.startswith("ui-create"):
|
|
173
|
+
ui_config = state.get("ui_config", {})
|
|
174
|
+
if not ui_config and endpoint_data:
|
|
175
|
+
ui_config = endpoint_data.get("ui_config", {})
|
|
176
|
+
|
|
177
|
+
if ui_config:
|
|
178
|
+
context_parts.append("")
|
|
179
|
+
context_parts.append("**UI Configuration:**")
|
|
180
|
+
if ui_config.get("use_brand_guide"):
|
|
181
|
+
context_parts.append(" - Brand guide: Applied")
|
|
182
|
+
if ui_config.get("component_type"):
|
|
183
|
+
context_parts.append(f" - Type: {ui_config['component_type']}")
|
|
184
|
+
if ui_config.get("accessibility_level"):
|
|
185
|
+
context_parts.append(f" - A11y: {ui_config['accessibility_level']}")
|
|
186
|
+
if ui_config.get("data_sources"):
|
|
187
|
+
context_parts.append(f" - Data sources: {len(ui_config['data_sources'])}")
|
|
107
188
|
|
|
108
189
|
# Get phase status (from endpoint_data for multi-API, or state for legacy)
|
|
109
190
|
phases = endpoint_data.get("phases", {})
|
|
@@ -183,10 +264,19 @@ def main():
|
|
|
183
264
|
context_parts.append(" - Research: .claude/research/")
|
|
184
265
|
context_parts.append(" - Manifest: src/app/api-test/api-tests-manifest.json (if exists)")
|
|
185
266
|
|
|
186
|
-
# Workflow reminder
|
|
267
|
+
# Workflow reminder based on type
|
|
187
268
|
context_parts.append("")
|
|
188
|
-
|
|
189
|
-
|
|
269
|
+
if workflow_type == "combine-api":
|
|
270
|
+
context_parts.append("**Workflow Reminder:** This is a combined API workflow.")
|
|
271
|
+
context_parts.append("Ensure all source APIs exist in registry before orchestration.")
|
|
272
|
+
context_parts.append("Test both individual APIs and the combined flow.")
|
|
273
|
+
elif workflow_type.startswith("ui-create"):
|
|
274
|
+
context_parts.append("**Workflow Reminder:** This is a UI development workflow.")
|
|
275
|
+
context_parts.append("Check registry for reusable components before creating new ones.")
|
|
276
|
+
context_parts.append("Ensure brand guide compliance and accessibility requirements.")
|
|
277
|
+
else:
|
|
278
|
+
context_parts.append("**Workflow Reminder:** This project uses interview-driven API development.")
|
|
279
|
+
context_parts.append("Phases loop back if verification fails. Research before answering API questions.")
|
|
190
280
|
|
|
191
281
|
# Build the output
|
|
192
282
|
additional_context = "\n".join(context_parts)
|
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
"""
|
|
3
3
|
Hook: PostToolUse for Write/Edit
|
|
4
4
|
Purpose: Auto-create UI Showcase page when first component/page is created
|
|
5
|
+
and auto-populate showcase data from registry.
|
|
5
6
|
|
|
6
7
|
This hook monitors for new component or page registrations. When the first
|
|
7
8
|
UI element is added to registry.json, it creates the UI Showcase page
|
|
8
9
|
at src/app/ui-showcase/ if it doesn't exist.
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
Also generates src/app/ui-showcase/data.json from registry for auto-population.
|
|
12
|
+
|
|
13
|
+
Version: 3.10.0
|
|
11
14
|
|
|
12
15
|
Returns:
|
|
13
16
|
- {"continue": true} - Always continues
|
|
@@ -17,12 +20,66 @@ import json
|
|
|
17
20
|
import sys
|
|
18
21
|
from pathlib import Path
|
|
19
22
|
import shutil
|
|
23
|
+
from datetime import datetime
|
|
20
24
|
|
|
21
25
|
# State and registry files in .claude/ directory
|
|
22
26
|
STATE_FILE = Path(__file__).parent.parent / "api-dev-state.json"
|
|
23
27
|
REGISTRY_FILE = Path(__file__).parent.parent / "registry.json"
|
|
24
28
|
|
|
25
29
|
|
|
30
|
+
def generate_showcase_data(registry, cwd):
|
|
31
|
+
"""Generate showcase data file from registry for auto-population.
|
|
32
|
+
|
|
33
|
+
Creates src/app/ui-showcase/data.json with component/page listings.
|
|
34
|
+
"""
|
|
35
|
+
components = registry.get("components", {})
|
|
36
|
+
pages = registry.get("pages", {})
|
|
37
|
+
|
|
38
|
+
showcase_data = {
|
|
39
|
+
"version": "3.10.0",
|
|
40
|
+
"generated_at": datetime.now().isoformat(),
|
|
41
|
+
"components": [],
|
|
42
|
+
"pages": []
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# Process components
|
|
46
|
+
for name, comp in components.items():
|
|
47
|
+
showcase_data["components"].append({
|
|
48
|
+
"id": name,
|
|
49
|
+
"name": comp.get("name", name),
|
|
50
|
+
"description": comp.get("description", ""),
|
|
51
|
+
"type": comp.get("type", "atom"),
|
|
52
|
+
"path": comp.get("path", f"src/components/{name}/{name}.tsx"),
|
|
53
|
+
"storybook_url": comp.get("storybook_url", f"/?path=/story/{name.lower()}--default"),
|
|
54
|
+
"variants": comp.get("variants", []),
|
|
55
|
+
"props": list(comp.get("props", {}).keys()) if isinstance(comp.get("props"), dict) else [],
|
|
56
|
+
"created_at": comp.get("created_at", ""),
|
|
57
|
+
"status": comp.get("status", "ready")
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
# Process pages
|
|
61
|
+
for name, page in pages.items():
|
|
62
|
+
showcase_data["pages"].append({
|
|
63
|
+
"id": name,
|
|
64
|
+
"name": page.get("name", name),
|
|
65
|
+
"description": page.get("description", ""),
|
|
66
|
+
"route": page.get("route", f"/{name}"),
|
|
67
|
+
"page_type": page.get("page_type", "landing"),
|
|
68
|
+
"path": page.get("path", f"src/app/{name}/page.tsx"),
|
|
69
|
+
"requires_auth": page.get("requires_auth", False),
|
|
70
|
+
"data_sources": page.get("data_sources", []),
|
|
71
|
+
"created_at": page.get("created_at", ""),
|
|
72
|
+
"status": page.get("status", "ready")
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
# Write data file
|
|
76
|
+
data_file = cwd / "src" / "app" / "ui-showcase" / "data.json"
|
|
77
|
+
data_file.parent.mkdir(parents=True, exist_ok=True)
|
|
78
|
+
data_file.write_text(json.dumps(showcase_data, indent=2))
|
|
79
|
+
|
|
80
|
+
return str(data_file.relative_to(cwd))
|
|
81
|
+
|
|
82
|
+
|
|
26
83
|
def copy_showcase_templates(cwd):
|
|
27
84
|
"""Copy UI showcase templates to src/app/ui-showcase/."""
|
|
28
85
|
# Source templates (installed by CLI)
|
|
@@ -131,13 +188,20 @@ def main():
|
|
|
131
188
|
if components or pages:
|
|
132
189
|
created_files = copy_showcase_templates(cwd)
|
|
133
190
|
|
|
191
|
+
# Always update data.json from registry
|
|
192
|
+
data_file = generate_showcase_data(registry, cwd)
|
|
193
|
+
|
|
134
194
|
if created_files:
|
|
135
195
|
print(json.dumps({
|
|
136
196
|
"continue": True,
|
|
137
|
-
"notify": f"Created UI Showcase at /ui-showcase ({len(created_files)} files)"
|
|
197
|
+
"notify": f"Created UI Showcase at /ui-showcase ({len(created_files)} files) + data.json"
|
|
138
198
|
}))
|
|
139
199
|
else:
|
|
140
|
-
|
|
200
|
+
# Just updated data.json
|
|
201
|
+
print(json.dumps({
|
|
202
|
+
"continue": True,
|
|
203
|
+
"notify": f"Updated UI Showcase data ({len(components)} components, {len(pages)} pages)"
|
|
204
|
+
}))
|
|
141
205
|
else:
|
|
142
206
|
print(json.dumps({"continue": True}))
|
|
143
207
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hustle-together/api-dev-tools",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.10.0",
|
|
4
4
|
"description": "Interview-driven, research-first API and UI development workflow with continuous verification loops for Claude Code",
|
|
5
5
|
"main": "bin/cli.js",
|
|
6
6
|
"bin": {
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
"hooks/",
|
|
13
13
|
"scripts/",
|
|
14
14
|
"templates/",
|
|
15
|
-
"demo/",
|
|
16
15
|
"README.md",
|
|
17
16
|
"LICENSE"
|
|
18
17
|
],
|
|
@@ -1,8 +1,46 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "3.
|
|
2
|
+
"version": "3.10.0",
|
|
3
3
|
"created_at": null,
|
|
4
|
+
"workflow": null,
|
|
5
|
+
"_workflow_options": ["api-create", "combine-api", "ui-create-component", "ui-create-page"],
|
|
4
6
|
"active_endpoint": null,
|
|
7
|
+
"active_element": null,
|
|
5
8
|
"endpoints": {},
|
|
9
|
+
"elements": {},
|
|
10
|
+
"combine_config": {
|
|
11
|
+
"_comment": "Configuration for combine-api workflow",
|
|
12
|
+
"mode": null,
|
|
13
|
+
"_mode_options": ["api", "ui"],
|
|
14
|
+
"source_elements": [],
|
|
15
|
+
"_source_elements_example": [
|
|
16
|
+
{ "type": "api", "name": "openai", "registry_ref": "apis.openai" },
|
|
17
|
+
{ "type": "api", "name": "wordpress", "registry_ref": "apis.wordpress" }
|
|
18
|
+
],
|
|
19
|
+
"flow_type": null,
|
|
20
|
+
"_flow_type_options": ["sequential", "parallel", "conditional"],
|
|
21
|
+
"error_strategy": null,
|
|
22
|
+
"_error_strategy_options": ["fail-fast", "partial", "retry"],
|
|
23
|
+
"orchestration": {
|
|
24
|
+
"input_mapping": {},
|
|
25
|
+
"output_mapping": {},
|
|
26
|
+
"transforms": []
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"ui_config": {
|
|
30
|
+
"_comment": "Configuration for ui-create-* workflows",
|
|
31
|
+
"mode": null,
|
|
32
|
+
"_mode_options": ["component", "page"],
|
|
33
|
+
"use_brand_guide": false,
|
|
34
|
+
"component_type": null,
|
|
35
|
+
"_component_type_options": ["atom", "molecule", "organism", "template"],
|
|
36
|
+
"page_type": null,
|
|
37
|
+
"_page_type_options": ["landing", "dashboard", "form", "list", "detail", "auth"],
|
|
38
|
+
"accessibility_level": null,
|
|
39
|
+
"_accessibility_level_options": ["AA", "AAA"],
|
|
40
|
+
"data_sources": [],
|
|
41
|
+
"variants": [],
|
|
42
|
+
"sizes": []
|
|
43
|
+
},
|
|
6
44
|
"turn_count": 0,
|
|
7
45
|
"last_turn_timestamp": null,
|
|
8
46
|
"research_queries": [],
|
package/templates/settings.json
CHANGED
|
@@ -119,6 +119,18 @@
|
|
|
119
119
|
{
|
|
120
120
|
"type": "command",
|
|
121
121
|
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/check-playwright-setup.py"
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"type": "command",
|
|
125
|
+
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/check-api-routes.py"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"type": "command",
|
|
129
|
+
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/enforce-page-components.py"
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"type": "command",
|
|
133
|
+
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/enforce-page-data-schema.py"
|
|
122
134
|
}
|
|
123
135
|
]
|
|
124
136
|
},
|
|
@@ -181,6 +193,10 @@
|
|
|
181
193
|
{
|
|
182
194
|
"type": "command",
|
|
183
195
|
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/update-ui-showcase.py"
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"type": "command",
|
|
199
|
+
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/enforce-a11y-audit.py"
|
|
184
200
|
}
|
|
185
201
|
]
|
|
186
202
|
}
|
|
@@ -237,7 +237,7 @@ export function HeroHeader({ title, description, badge }: HeroHeaderProps) {
|
|
|
237
237
|
{/* Animated Grid Canvas */}
|
|
238
238
|
<canvas
|
|
239
239
|
ref={canvasRef}
|
|
240
|
-
className="pointer-events-none absolute inset-0 z-0 opacity-60 blur-[
|
|
240
|
+
className="pointer-events-none absolute inset-0 z-0 opacity-80 dark:opacity-60 dark:blur-[0.5px]"
|
|
241
241
|
/>
|
|
242
242
|
|
|
243
243
|
{/* Content - Uses same container as main content for alignment */}
|