nc1709 1.15.4__py3-none-any.whl
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.
- nc1709/__init__.py +13 -0
- nc1709/agent/__init__.py +36 -0
- nc1709/agent/core.py +505 -0
- nc1709/agent/mcp_bridge.py +245 -0
- nc1709/agent/permissions.py +298 -0
- nc1709/agent/tools/__init__.py +21 -0
- nc1709/agent/tools/base.py +440 -0
- nc1709/agent/tools/bash_tool.py +367 -0
- nc1709/agent/tools/file_tools.py +454 -0
- nc1709/agent/tools/notebook_tools.py +516 -0
- nc1709/agent/tools/search_tools.py +322 -0
- nc1709/agent/tools/task_tool.py +284 -0
- nc1709/agent/tools/web_tools.py +555 -0
- nc1709/agents/__init__.py +17 -0
- nc1709/agents/auto_fix.py +506 -0
- nc1709/agents/test_generator.py +507 -0
- nc1709/checkpoints.py +372 -0
- nc1709/cli.py +3380 -0
- nc1709/cli_ui.py +1080 -0
- nc1709/cognitive/__init__.py +149 -0
- nc1709/cognitive/anticipation.py +594 -0
- nc1709/cognitive/context_engine.py +1046 -0
- nc1709/cognitive/council.py +824 -0
- nc1709/cognitive/learning.py +761 -0
- nc1709/cognitive/router.py +583 -0
- nc1709/cognitive/system.py +519 -0
- nc1709/config.py +155 -0
- nc1709/custom_commands.py +300 -0
- nc1709/executor.py +333 -0
- nc1709/file_controller.py +354 -0
- nc1709/git_integration.py +308 -0
- nc1709/github_integration.py +477 -0
- nc1709/image_input.py +446 -0
- nc1709/linting.py +519 -0
- nc1709/llm_adapter.py +667 -0
- nc1709/logger.py +192 -0
- nc1709/mcp/__init__.py +18 -0
- nc1709/mcp/client.py +370 -0
- nc1709/mcp/manager.py +407 -0
- nc1709/mcp/protocol.py +210 -0
- nc1709/mcp/server.py +473 -0
- nc1709/memory/__init__.py +20 -0
- nc1709/memory/embeddings.py +325 -0
- nc1709/memory/indexer.py +474 -0
- nc1709/memory/sessions.py +432 -0
- nc1709/memory/vector_store.py +451 -0
- nc1709/models/__init__.py +86 -0
- nc1709/models/detector.py +377 -0
- nc1709/models/formats.py +315 -0
- nc1709/models/manager.py +438 -0
- nc1709/models/registry.py +497 -0
- nc1709/performance/__init__.py +343 -0
- nc1709/performance/cache.py +705 -0
- nc1709/performance/pipeline.py +611 -0
- nc1709/performance/tiering.py +543 -0
- nc1709/plan_mode.py +362 -0
- nc1709/plugins/__init__.py +17 -0
- nc1709/plugins/agents/__init__.py +18 -0
- nc1709/plugins/agents/django_agent.py +912 -0
- nc1709/plugins/agents/docker_agent.py +623 -0
- nc1709/plugins/agents/fastapi_agent.py +887 -0
- nc1709/plugins/agents/git_agent.py +731 -0
- nc1709/plugins/agents/nextjs_agent.py +867 -0
- nc1709/plugins/base.py +359 -0
- nc1709/plugins/manager.py +411 -0
- nc1709/plugins/registry.py +337 -0
- nc1709/progress.py +443 -0
- nc1709/prompts/__init__.py +22 -0
- nc1709/prompts/agent_system.py +180 -0
- nc1709/prompts/task_prompts.py +340 -0
- nc1709/prompts/unified_prompt.py +133 -0
- nc1709/reasoning_engine.py +541 -0
- nc1709/remote_client.py +266 -0
- nc1709/shell_completions.py +349 -0
- nc1709/slash_commands.py +649 -0
- nc1709/task_classifier.py +408 -0
- nc1709/version_check.py +177 -0
- nc1709/web/__init__.py +8 -0
- nc1709/web/server.py +950 -0
- nc1709/web/templates/index.html +1127 -0
- nc1709-1.15.4.dist-info/METADATA +858 -0
- nc1709-1.15.4.dist-info/RECORD +86 -0
- nc1709-1.15.4.dist-info/WHEEL +5 -0
- nc1709-1.15.4.dist-info/entry_points.txt +2 -0
- nc1709-1.15.4.dist-info/licenses/LICENSE +9 -0
- nc1709-1.15.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,867 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Next.js Agent for NC1709
|
|
3
|
+
Scaffolds Next.js projects, pages, components, and API routes
|
|
4
|
+
"""
|
|
5
|
+
import os
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Dict, Any, Optional, List
|
|
9
|
+
|
|
10
|
+
from ..base import (
|
|
11
|
+
Plugin, PluginMetadata, PluginCapability,
|
|
12
|
+
ActionResult
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class NextJSAgent(Plugin):
|
|
17
|
+
"""
|
|
18
|
+
Next.js scaffolding and development agent.
|
|
19
|
+
|
|
20
|
+
Provides:
|
|
21
|
+
- Project scaffolding (App Router or Pages Router)
|
|
22
|
+
- Page generation
|
|
23
|
+
- Component generation (with TypeScript support)
|
|
24
|
+
- API route generation
|
|
25
|
+
- Layout generation
|
|
26
|
+
- Middleware setup
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
METADATA = PluginMetadata(
|
|
30
|
+
name="nextjs",
|
|
31
|
+
version="1.0.0",
|
|
32
|
+
description="Next.js project scaffolding and development",
|
|
33
|
+
author="NC1709 Team",
|
|
34
|
+
capabilities=[
|
|
35
|
+
PluginCapability.CODE_GENERATION,
|
|
36
|
+
PluginCapability.PROJECT_SCAFFOLDING
|
|
37
|
+
],
|
|
38
|
+
keywords=[
|
|
39
|
+
"nextjs", "next", "react", "typescript", "javascript",
|
|
40
|
+
"component", "page", "api", "route", "layout",
|
|
41
|
+
"middleware", "frontend", "ssr", "ssg"
|
|
42
|
+
],
|
|
43
|
+
config_schema={
|
|
44
|
+
"project_path": {"type": "string", "default": "."},
|
|
45
|
+
"use_typescript": {"type": "boolean", "default": True},
|
|
46
|
+
"use_app_router": {"type": "boolean", "default": True},
|
|
47
|
+
"styling": {"type": "string", "enum": ["tailwind", "css-modules", "styled-components"], "default": "tailwind"}
|
|
48
|
+
}
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def metadata(self) -> PluginMetadata:
|
|
53
|
+
return self.METADATA
|
|
54
|
+
|
|
55
|
+
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
|
56
|
+
super().__init__(config)
|
|
57
|
+
self._project_path: Optional[Path] = None
|
|
58
|
+
|
|
59
|
+
def initialize(self) -> bool:
|
|
60
|
+
"""Initialize the Next.js agent"""
|
|
61
|
+
self._project_path = Path(self._config.get("project_path", ".")).resolve()
|
|
62
|
+
return True
|
|
63
|
+
|
|
64
|
+
def cleanup(self) -> None:
|
|
65
|
+
"""Cleanup resources"""
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
def _register_actions(self) -> None:
|
|
69
|
+
"""Register Next.js actions"""
|
|
70
|
+
self.register_action(
|
|
71
|
+
"scaffold",
|
|
72
|
+
self.scaffold_project,
|
|
73
|
+
"Create a new Next.js project structure",
|
|
74
|
+
parameters={
|
|
75
|
+
"name": {"type": "string", "required": True},
|
|
76
|
+
"typescript": {"type": "boolean", "default": True},
|
|
77
|
+
"tailwind": {"type": "boolean", "default": True}
|
|
78
|
+
}
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
self.register_action(
|
|
82
|
+
"page",
|
|
83
|
+
self.create_page,
|
|
84
|
+
"Generate a new page",
|
|
85
|
+
parameters={
|
|
86
|
+
"path": {"type": "string", "required": True},
|
|
87
|
+
"name": {"type": "string", "required": True}
|
|
88
|
+
}
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
self.register_action(
|
|
92
|
+
"component",
|
|
93
|
+
self.create_component,
|
|
94
|
+
"Generate a React component",
|
|
95
|
+
parameters={
|
|
96
|
+
"name": {"type": "string", "required": True},
|
|
97
|
+
"props": {"type": "object", "default": {}},
|
|
98
|
+
"client": {"type": "boolean", "default": False}
|
|
99
|
+
}
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
self.register_action(
|
|
103
|
+
"api",
|
|
104
|
+
self.create_api_route,
|
|
105
|
+
"Generate an API route",
|
|
106
|
+
parameters={
|
|
107
|
+
"path": {"type": "string", "required": True},
|
|
108
|
+
"methods": {"type": "array", "default": ["GET"]}
|
|
109
|
+
}
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
self.register_action(
|
|
113
|
+
"layout",
|
|
114
|
+
self.create_layout,
|
|
115
|
+
"Generate a layout component",
|
|
116
|
+
parameters={
|
|
117
|
+
"path": {"type": "string", "default": ""},
|
|
118
|
+
"name": {"type": "string", "default": "Layout"}
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
self.register_action(
|
|
123
|
+
"analyze",
|
|
124
|
+
self.analyze_project,
|
|
125
|
+
"Analyze existing Next.js project structure"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
def scaffold_project(
|
|
129
|
+
self,
|
|
130
|
+
name: str,
|
|
131
|
+
typescript: bool = True,
|
|
132
|
+
tailwind: bool = True
|
|
133
|
+
) -> ActionResult:
|
|
134
|
+
"""Create a new Next.js project structure
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
name: Project name
|
|
138
|
+
typescript: Use TypeScript
|
|
139
|
+
tailwind: Include Tailwind CSS
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
ActionResult
|
|
143
|
+
"""
|
|
144
|
+
project_dir = self._project_path / name
|
|
145
|
+
ext = "tsx" if typescript else "jsx"
|
|
146
|
+
style_ext = "module.css"
|
|
147
|
+
|
|
148
|
+
if project_dir.exists():
|
|
149
|
+
return ActionResult.fail(f"Directory '{name}' already exists")
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
# Create directory structure (App Router)
|
|
153
|
+
dirs = [
|
|
154
|
+
project_dir,
|
|
155
|
+
project_dir / "app",
|
|
156
|
+
project_dir / "app" / "api",
|
|
157
|
+
project_dir / "components",
|
|
158
|
+
project_dir / "lib",
|
|
159
|
+
project_dir / "public",
|
|
160
|
+
project_dir / "styles",
|
|
161
|
+
]
|
|
162
|
+
|
|
163
|
+
for d in dirs:
|
|
164
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
165
|
+
|
|
166
|
+
# Create package.json
|
|
167
|
+
package_json = self._generate_package_json(name, typescript, tailwind)
|
|
168
|
+
(project_dir / "package.json").write_text(json.dumps(package_json, indent=2))
|
|
169
|
+
|
|
170
|
+
# Create root layout
|
|
171
|
+
layout_content = self._generate_root_layout(typescript, tailwind)
|
|
172
|
+
(project_dir / "app" / f"layout.{ext}").write_text(layout_content)
|
|
173
|
+
|
|
174
|
+
# Create home page
|
|
175
|
+
page_content = self._generate_home_page(typescript)
|
|
176
|
+
(project_dir / "app" / f"page.{ext}").write_text(page_content)
|
|
177
|
+
|
|
178
|
+
# Create global styles
|
|
179
|
+
if tailwind:
|
|
180
|
+
globals_css = self._generate_tailwind_globals()
|
|
181
|
+
(project_dir / "app" / "globals.css").write_text(globals_css)
|
|
182
|
+
|
|
183
|
+
# Create tailwind config
|
|
184
|
+
tailwind_config = self._generate_tailwind_config(typescript)
|
|
185
|
+
config_ext = "ts" if typescript else "js"
|
|
186
|
+
(project_dir / f"tailwind.config.{config_ext}").write_text(tailwind_config)
|
|
187
|
+
|
|
188
|
+
# Create postcss config
|
|
189
|
+
postcss_config = self._generate_postcss_config()
|
|
190
|
+
(project_dir / "postcss.config.js").write_text(postcss_config)
|
|
191
|
+
else:
|
|
192
|
+
globals_css = self._generate_basic_globals()
|
|
193
|
+
(project_dir / "app" / "globals.css").write_text(globals_css)
|
|
194
|
+
|
|
195
|
+
# Create next.config
|
|
196
|
+
next_config = self._generate_next_config(typescript)
|
|
197
|
+
config_ext = "mjs"
|
|
198
|
+
(project_dir / f"next.config.{config_ext}").write_text(next_config)
|
|
199
|
+
|
|
200
|
+
if typescript:
|
|
201
|
+
# Create tsconfig
|
|
202
|
+
tsconfig = self._generate_tsconfig()
|
|
203
|
+
(project_dir / "tsconfig.json").write_text(json.dumps(tsconfig, indent=2))
|
|
204
|
+
|
|
205
|
+
# Create example component
|
|
206
|
+
component_content = self._generate_example_component(typescript)
|
|
207
|
+
(project_dir / "components" / f"Button.{ext}").write_text(component_content)
|
|
208
|
+
|
|
209
|
+
# Create example API route
|
|
210
|
+
api_content = self._generate_example_api(typescript)
|
|
211
|
+
api_dir = project_dir / "app" / "api" / "hello"
|
|
212
|
+
api_dir.mkdir(exist_ok=True)
|
|
213
|
+
(api_dir / f"route.{ext[:2]}").write_text(api_content)
|
|
214
|
+
|
|
215
|
+
# Create .gitignore
|
|
216
|
+
gitignore = self._generate_gitignore()
|
|
217
|
+
(project_dir / ".gitignore").write_text(gitignore)
|
|
218
|
+
|
|
219
|
+
# Create README
|
|
220
|
+
readme = self._generate_readme(name)
|
|
221
|
+
(project_dir / "README.md").write_text(readme)
|
|
222
|
+
|
|
223
|
+
files_created = len(list(project_dir.rglob("*")))
|
|
224
|
+
|
|
225
|
+
return ActionResult.ok(
|
|
226
|
+
message=f"Created Next.js project '{name}' with {files_created} files",
|
|
227
|
+
data={
|
|
228
|
+
"project_path": str(project_dir),
|
|
229
|
+
"typescript": typescript,
|
|
230
|
+
"tailwind": tailwind,
|
|
231
|
+
"next_steps": [
|
|
232
|
+
f"cd {name}",
|
|
233
|
+
"npm install",
|
|
234
|
+
"npm run dev"
|
|
235
|
+
]
|
|
236
|
+
}
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
except Exception as e:
|
|
240
|
+
return ActionResult.fail(str(e))
|
|
241
|
+
|
|
242
|
+
def create_page(
|
|
243
|
+
self,
|
|
244
|
+
path: str,
|
|
245
|
+
name: str
|
|
246
|
+
) -> ActionResult:
|
|
247
|
+
"""Generate a new page
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
path: Page path (e.g., "dashboard" or "users/[id]")
|
|
251
|
+
name: Page component name
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
254
|
+
ActionResult with generated code
|
|
255
|
+
"""
|
|
256
|
+
use_ts = self._config.get("use_typescript", True)
|
|
257
|
+
ext = "tsx" if use_ts else "jsx"
|
|
258
|
+
|
|
259
|
+
# Detect dynamic segments
|
|
260
|
+
is_dynamic = "[" in path
|
|
261
|
+
|
|
262
|
+
code = self._generate_page_code(name, path, is_dynamic, use_ts)
|
|
263
|
+
|
|
264
|
+
return ActionResult.ok(
|
|
265
|
+
message=f"Generated page at app/{path}/page.{ext}",
|
|
266
|
+
data={
|
|
267
|
+
"code": code,
|
|
268
|
+
"path": f"app/{path}/page.{ext}",
|
|
269
|
+
"dynamic": is_dynamic
|
|
270
|
+
}
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
def create_component(
|
|
274
|
+
self,
|
|
275
|
+
name: str,
|
|
276
|
+
props: Dict[str, str] = None,
|
|
277
|
+
client: bool = False
|
|
278
|
+
) -> ActionResult:
|
|
279
|
+
"""Generate a React component
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
name: Component name
|
|
283
|
+
props: Props definition
|
|
284
|
+
client: Whether it's a client component
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
ActionResult with generated code
|
|
288
|
+
"""
|
|
289
|
+
props = props or {}
|
|
290
|
+
use_ts = self._config.get("use_typescript", True)
|
|
291
|
+
ext = "tsx" if use_ts else "jsx"
|
|
292
|
+
|
|
293
|
+
code = self._generate_component_code(name, props, client, use_ts)
|
|
294
|
+
|
|
295
|
+
return ActionResult.ok(
|
|
296
|
+
message=f"Generated component '{name}'",
|
|
297
|
+
data={
|
|
298
|
+
"code": code,
|
|
299
|
+
"path": f"components/{name}.{ext}",
|
|
300
|
+
"client": client
|
|
301
|
+
}
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
def create_api_route(
|
|
305
|
+
self,
|
|
306
|
+
path: str,
|
|
307
|
+
methods: List[str] = None
|
|
308
|
+
) -> ActionResult:
|
|
309
|
+
"""Generate an API route
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
path: Route path
|
|
313
|
+
methods: HTTP methods to handle
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
ActionResult with generated code
|
|
317
|
+
"""
|
|
318
|
+
methods = methods or ["GET"]
|
|
319
|
+
use_ts = self._config.get("use_typescript", True)
|
|
320
|
+
ext = "ts" if use_ts else "js"
|
|
321
|
+
|
|
322
|
+
code = self._generate_api_route_code(path, methods, use_ts)
|
|
323
|
+
|
|
324
|
+
return ActionResult.ok(
|
|
325
|
+
message=f"Generated API route at app/api/{path}/route.{ext}",
|
|
326
|
+
data={
|
|
327
|
+
"code": code,
|
|
328
|
+
"path": f"app/api/{path}/route.{ext}",
|
|
329
|
+
"methods": methods
|
|
330
|
+
}
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
def create_layout(
|
|
334
|
+
self,
|
|
335
|
+
path: str = "",
|
|
336
|
+
name: str = "Layout"
|
|
337
|
+
) -> ActionResult:
|
|
338
|
+
"""Generate a layout component
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
path: Layout path
|
|
342
|
+
name: Layout name
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
ActionResult with generated code
|
|
346
|
+
"""
|
|
347
|
+
use_ts = self._config.get("use_typescript", True)
|
|
348
|
+
ext = "tsx" if use_ts else "jsx"
|
|
349
|
+
|
|
350
|
+
code = self._generate_layout_code(name, use_ts)
|
|
351
|
+
|
|
352
|
+
layout_path = f"app/{path}/layout.{ext}" if path else f"app/layout.{ext}"
|
|
353
|
+
|
|
354
|
+
return ActionResult.ok(
|
|
355
|
+
message=f"Generated layout at {layout_path}",
|
|
356
|
+
data={
|
|
357
|
+
"code": code,
|
|
358
|
+
"path": layout_path
|
|
359
|
+
}
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
def analyze_project(self) -> ActionResult:
|
|
363
|
+
"""Analyze existing Next.js project structure
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
ActionResult with project analysis
|
|
367
|
+
"""
|
|
368
|
+
# Look for Next.js indicators
|
|
369
|
+
package_json = self._project_path / "package.json"
|
|
370
|
+
|
|
371
|
+
if not package_json.exists():
|
|
372
|
+
return ActionResult.fail("No package.json found")
|
|
373
|
+
|
|
374
|
+
try:
|
|
375
|
+
pkg = json.loads(package_json.read_text())
|
|
376
|
+
deps = {**pkg.get("dependencies", {}), **pkg.get("devDependencies", {})}
|
|
377
|
+
|
|
378
|
+
if "next" not in deps:
|
|
379
|
+
return ActionResult.fail("Not a Next.js project (next not in dependencies)")
|
|
380
|
+
except Exception:
|
|
381
|
+
return ActionResult.fail("Could not parse package.json")
|
|
382
|
+
|
|
383
|
+
analysis = {
|
|
384
|
+
"project_path": str(self._project_path),
|
|
385
|
+
"next_version": deps.get("next", "unknown"),
|
|
386
|
+
"typescript": "typescript" in deps,
|
|
387
|
+
"tailwind": "tailwindcss" in deps,
|
|
388
|
+
"app_router": (self._project_path / "app").exists(),
|
|
389
|
+
"pages_router": (self._project_path / "pages").exists(),
|
|
390
|
+
"pages": [],
|
|
391
|
+
"components": [],
|
|
392
|
+
"api_routes": []
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
# Scan for pages
|
|
396
|
+
app_dir = self._project_path / "app"
|
|
397
|
+
if app_dir.exists():
|
|
398
|
+
for page in app_dir.rglob("page.*"):
|
|
399
|
+
rel_path = page.relative_to(app_dir)
|
|
400
|
+
route = "/" + str(rel_path.parent).replace("\\", "/")
|
|
401
|
+
if route == "/.":
|
|
402
|
+
route = "/"
|
|
403
|
+
analysis["pages"].append(route)
|
|
404
|
+
|
|
405
|
+
# Scan for API routes
|
|
406
|
+
api_dir = app_dir / "api"
|
|
407
|
+
if api_dir.exists():
|
|
408
|
+
for route in api_dir.rglob("route.*"):
|
|
409
|
+
rel_path = route.relative_to(api_dir)
|
|
410
|
+
api_path = "/api/" + str(rel_path.parent).replace("\\", "/")
|
|
411
|
+
analysis["api_routes"].append(api_path)
|
|
412
|
+
|
|
413
|
+
# Scan for components
|
|
414
|
+
components_dir = self._project_path / "components"
|
|
415
|
+
if components_dir.exists():
|
|
416
|
+
for comp in components_dir.rglob("*.tsx"):
|
|
417
|
+
analysis["components"].append(comp.stem)
|
|
418
|
+
for comp in components_dir.rglob("*.jsx"):
|
|
419
|
+
analysis["components"].append(comp.stem)
|
|
420
|
+
|
|
421
|
+
return ActionResult.ok(
|
|
422
|
+
message=f"Analyzed Next.js project with {len(analysis['pages'])} pages",
|
|
423
|
+
data=analysis
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
# Code generation helpers
|
|
427
|
+
|
|
428
|
+
def _generate_package_json(self, name: str, typescript: bool, tailwind: bool) -> dict:
|
|
429
|
+
"""Generate package.json"""
|
|
430
|
+
pkg = {
|
|
431
|
+
"name": name,
|
|
432
|
+
"version": "0.1.0",
|
|
433
|
+
"private": True,
|
|
434
|
+
"scripts": {
|
|
435
|
+
"dev": "next dev",
|
|
436
|
+
"build": "next build",
|
|
437
|
+
"start": "next start",
|
|
438
|
+
"lint": "next lint"
|
|
439
|
+
},
|
|
440
|
+
"dependencies": {
|
|
441
|
+
"next": "14.0.0",
|
|
442
|
+
"react": "^18",
|
|
443
|
+
"react-dom": "^18"
|
|
444
|
+
},
|
|
445
|
+
"devDependencies": {
|
|
446
|
+
"eslint": "^8",
|
|
447
|
+
"eslint-config-next": "14.0.0"
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if typescript:
|
|
452
|
+
pkg["devDependencies"].update({
|
|
453
|
+
"typescript": "^5",
|
|
454
|
+
"@types/node": "^20",
|
|
455
|
+
"@types/react": "^18",
|
|
456
|
+
"@types/react-dom": "^18"
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
if tailwind:
|
|
460
|
+
pkg["devDependencies"].update({
|
|
461
|
+
"tailwindcss": "^3.3.0",
|
|
462
|
+
"autoprefixer": "^10.0.1",
|
|
463
|
+
"postcss": "^8"
|
|
464
|
+
})
|
|
465
|
+
|
|
466
|
+
return pkg
|
|
467
|
+
|
|
468
|
+
def _generate_root_layout(self, typescript: bool, tailwind: bool) -> str:
|
|
469
|
+
"""Generate root layout"""
|
|
470
|
+
props_type = ": { children: React.ReactNode }" if typescript else ""
|
|
471
|
+
|
|
472
|
+
return f'''import type {{ Metadata }} from 'next'
|
|
473
|
+
import './globals.css'
|
|
474
|
+
|
|
475
|
+
export const metadata: Metadata = {{
|
|
476
|
+
title: 'My App',
|
|
477
|
+
description: 'Built with Next.js',
|
|
478
|
+
}}
|
|
479
|
+
|
|
480
|
+
export default function RootLayout({{
|
|
481
|
+
children,
|
|
482
|
+
}}{props_type}) {{
|
|
483
|
+
return (
|
|
484
|
+
<html lang="en">
|
|
485
|
+
<body{' className="antialiased"' if tailwind else ''}>{{children}}</body>
|
|
486
|
+
</html>
|
|
487
|
+
)
|
|
488
|
+
}}
|
|
489
|
+
'''
|
|
490
|
+
|
|
491
|
+
def _generate_home_page(self, typescript: bool) -> str:
|
|
492
|
+
"""Generate home page"""
|
|
493
|
+
return '''export default function Home() {
|
|
494
|
+
return (
|
|
495
|
+
<main className="flex min-h-screen flex-col items-center justify-center p-24">
|
|
496
|
+
<h1 className="text-4xl font-bold">Welcome to Next.js</h1>
|
|
497
|
+
<p className="mt-4 text-lg text-gray-600">
|
|
498
|
+
Get started by editing app/page.tsx
|
|
499
|
+
</p>
|
|
500
|
+
</main>
|
|
501
|
+
)
|
|
502
|
+
}
|
|
503
|
+
'''
|
|
504
|
+
|
|
505
|
+
def _generate_tailwind_globals(self) -> str:
|
|
506
|
+
"""Generate Tailwind CSS globals"""
|
|
507
|
+
return '''@tailwind base;
|
|
508
|
+
@tailwind components;
|
|
509
|
+
@tailwind utilities;
|
|
510
|
+
'''
|
|
511
|
+
|
|
512
|
+
def _generate_basic_globals(self) -> str:
|
|
513
|
+
"""Generate basic CSS globals"""
|
|
514
|
+
return '''* {
|
|
515
|
+
box-sizing: border-box;
|
|
516
|
+
padding: 0;
|
|
517
|
+
margin: 0;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
html,
|
|
521
|
+
body {
|
|
522
|
+
max-width: 100vw;
|
|
523
|
+
overflow-x: hidden;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
a {
|
|
527
|
+
color: inherit;
|
|
528
|
+
text-decoration: none;
|
|
529
|
+
}
|
|
530
|
+
'''
|
|
531
|
+
|
|
532
|
+
def _generate_tailwind_config(self, typescript: bool) -> str:
|
|
533
|
+
"""Generate Tailwind config"""
|
|
534
|
+
if typescript:
|
|
535
|
+
return '''import type { Config } from 'tailwindcss'
|
|
536
|
+
|
|
537
|
+
const config: Config = {
|
|
538
|
+
content: [
|
|
539
|
+
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
|
|
540
|
+
'./components/**/*.{js,ts,jsx,tsx,mdx}',
|
|
541
|
+
'./app/**/*.{js,ts,jsx,tsx,mdx}',
|
|
542
|
+
],
|
|
543
|
+
theme: {
|
|
544
|
+
extend: {},
|
|
545
|
+
},
|
|
546
|
+
plugins: [],
|
|
547
|
+
}
|
|
548
|
+
export default config
|
|
549
|
+
'''
|
|
550
|
+
return '''/** @type {import('tailwindcss').Config} */
|
|
551
|
+
module.exports = {
|
|
552
|
+
content: [
|
|
553
|
+
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
|
|
554
|
+
'./components/**/*.{js,ts,jsx,tsx,mdx}',
|
|
555
|
+
'./app/**/*.{js,ts,jsx,tsx,mdx}',
|
|
556
|
+
],
|
|
557
|
+
theme: {
|
|
558
|
+
extend: {},
|
|
559
|
+
},
|
|
560
|
+
plugins: [],
|
|
561
|
+
}
|
|
562
|
+
'''
|
|
563
|
+
|
|
564
|
+
def _generate_postcss_config(self) -> str:
|
|
565
|
+
"""Generate PostCSS config"""
|
|
566
|
+
return '''module.exports = {
|
|
567
|
+
plugins: {
|
|
568
|
+
tailwindcss: {},
|
|
569
|
+
autoprefixer: {},
|
|
570
|
+
},
|
|
571
|
+
}
|
|
572
|
+
'''
|
|
573
|
+
|
|
574
|
+
def _generate_next_config(self, typescript: bool) -> str:
|
|
575
|
+
"""Generate next.config"""
|
|
576
|
+
return '''/** @type {import('next').NextConfig} */
|
|
577
|
+
const nextConfig = {}
|
|
578
|
+
|
|
579
|
+
export default nextConfig
|
|
580
|
+
'''
|
|
581
|
+
|
|
582
|
+
def _generate_tsconfig(self) -> dict:
|
|
583
|
+
"""Generate tsconfig.json"""
|
|
584
|
+
return {
|
|
585
|
+
"compilerOptions": {
|
|
586
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
587
|
+
"allowJs": True,
|
|
588
|
+
"skipLibCheck": True,
|
|
589
|
+
"strict": True,
|
|
590
|
+
"noEmit": True,
|
|
591
|
+
"esModuleInterop": True,
|
|
592
|
+
"module": "esnext",
|
|
593
|
+
"moduleResolution": "bundler",
|
|
594
|
+
"resolveJsonModule": True,
|
|
595
|
+
"isolatedModules": True,
|
|
596
|
+
"jsx": "preserve",
|
|
597
|
+
"incremental": True,
|
|
598
|
+
"plugins": [{"name": "next"}],
|
|
599
|
+
"paths": {"@/*": ["./*"]}
|
|
600
|
+
},
|
|
601
|
+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
602
|
+
"exclude": ["node_modules"]
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
def _generate_example_component(self, typescript: bool) -> str:
|
|
606
|
+
"""Generate example component"""
|
|
607
|
+
props_interface = '''
|
|
608
|
+
interface ButtonProps {
|
|
609
|
+
children: React.ReactNode
|
|
610
|
+
onClick?: () => void
|
|
611
|
+
variant?: 'primary' | 'secondary'
|
|
612
|
+
}
|
|
613
|
+
''' if typescript else ''
|
|
614
|
+
|
|
615
|
+
props_type = ": ButtonProps" if typescript else ""
|
|
616
|
+
|
|
617
|
+
return f'''"use client"
|
|
618
|
+
{props_interface}
|
|
619
|
+
export default function Button({{ children, onClick, variant = 'primary' }}{props_type}) {{
|
|
620
|
+
const baseStyles = "px-4 py-2 rounded-lg font-medium transition-colors"
|
|
621
|
+
const variants = {{
|
|
622
|
+
primary: "bg-blue-500 text-white hover:bg-blue-600",
|
|
623
|
+
secondary: "bg-gray-200 text-gray-800 hover:bg-gray-300"
|
|
624
|
+
}}
|
|
625
|
+
|
|
626
|
+
return (
|
|
627
|
+
<button
|
|
628
|
+
onClick={{onClick}}
|
|
629
|
+
className={{`${{baseStyles}} ${{variants[variant]}}`}}
|
|
630
|
+
>
|
|
631
|
+
{{children}}
|
|
632
|
+
</button>
|
|
633
|
+
)
|
|
634
|
+
}}
|
|
635
|
+
'''
|
|
636
|
+
|
|
637
|
+
def _generate_example_api(self, typescript: bool) -> str:
|
|
638
|
+
"""Generate example API route"""
|
|
639
|
+
type_import = "import { NextRequest, NextResponse } from 'next/server'\n\n" if typescript else ""
|
|
640
|
+
|
|
641
|
+
return f'''{type_import}export async function GET() {{
|
|
642
|
+
return Response.json({{ message: 'Hello from API' }})
|
|
643
|
+
}}
|
|
644
|
+
|
|
645
|
+
export async function POST(request{': NextRequest' if typescript else ''}) {{
|
|
646
|
+
const data = await request.json()
|
|
647
|
+
return Response.json({{ received: data }})
|
|
648
|
+
}}
|
|
649
|
+
'''
|
|
650
|
+
|
|
651
|
+
def _generate_gitignore(self) -> str:
|
|
652
|
+
"""Generate .gitignore"""
|
|
653
|
+
return '''# dependencies
|
|
654
|
+
/node_modules
|
|
655
|
+
/.pnp
|
|
656
|
+
.pnp.js
|
|
657
|
+
|
|
658
|
+
# testing
|
|
659
|
+
/coverage
|
|
660
|
+
|
|
661
|
+
# next.js
|
|
662
|
+
/.next/
|
|
663
|
+
/out/
|
|
664
|
+
|
|
665
|
+
# production
|
|
666
|
+
/build
|
|
667
|
+
|
|
668
|
+
# misc
|
|
669
|
+
.DS_Store
|
|
670
|
+
*.pem
|
|
671
|
+
|
|
672
|
+
# debug
|
|
673
|
+
npm-debug.log*
|
|
674
|
+
yarn-debug.log*
|
|
675
|
+
yarn-error.log*
|
|
676
|
+
|
|
677
|
+
# local env files
|
|
678
|
+
.env*.local
|
|
679
|
+
|
|
680
|
+
# typescript
|
|
681
|
+
*.tsbuildinfo
|
|
682
|
+
next-env.d.ts
|
|
683
|
+
'''
|
|
684
|
+
|
|
685
|
+
def _generate_readme(self, name: str) -> str:
|
|
686
|
+
"""Generate README.md"""
|
|
687
|
+
return f'''# {name}
|
|
688
|
+
|
|
689
|
+
This is a [Next.js](https://nextjs.org/) project.
|
|
690
|
+
|
|
691
|
+
## Getting Started
|
|
692
|
+
|
|
693
|
+
First, install dependencies:
|
|
694
|
+
|
|
695
|
+
```bash
|
|
696
|
+
npm install
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
Then, run the development server:
|
|
700
|
+
|
|
701
|
+
```bash
|
|
702
|
+
npm run dev
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
Open [http://localhost:3000](http://localhost:3000) with your browser.
|
|
706
|
+
|
|
707
|
+
## Project Structure
|
|
708
|
+
|
|
709
|
+
```
|
|
710
|
+
├── app/
|
|
711
|
+
│ ├── layout.tsx # Root layout
|
|
712
|
+
│ ├── page.tsx # Home page
|
|
713
|
+
│ └── api/ # API routes
|
|
714
|
+
├── components/ # React components
|
|
715
|
+
├── lib/ # Utility functions
|
|
716
|
+
└── public/ # Static files
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
## Learn More
|
|
720
|
+
|
|
721
|
+
- [Next.js Documentation](https://nextjs.org/docs)
|
|
722
|
+
- [Learn Next.js](https://nextjs.org/learn)
|
|
723
|
+
'''
|
|
724
|
+
|
|
725
|
+
def _generate_page_code(self, name: str, path: str, is_dynamic: bool, typescript: bool) -> str:
|
|
726
|
+
"""Generate page code"""
|
|
727
|
+
props = ""
|
|
728
|
+
if is_dynamic and typescript:
|
|
729
|
+
# Extract param name
|
|
730
|
+
import re
|
|
731
|
+
params = re.findall(r'\[(\w+)\]', path)
|
|
732
|
+
if params:
|
|
733
|
+
props = f"{{ params }}: {{ params: {{ {params[0]}: string }} }}"
|
|
734
|
+
|
|
735
|
+
return f'''export default function {name}Page({props}) {{
|
|
736
|
+
return (
|
|
737
|
+
<div className="p-8">
|
|
738
|
+
<h1 className="text-2xl font-bold">{name}</h1>
|
|
739
|
+
{f'<p>ID: {{params.{params[0]}}}</p>' if is_dynamic and 'params' in locals() else ''}
|
|
740
|
+
</div>
|
|
741
|
+
)
|
|
742
|
+
}}
|
|
743
|
+
'''
|
|
744
|
+
|
|
745
|
+
def _generate_component_code(
|
|
746
|
+
self,
|
|
747
|
+
name: str,
|
|
748
|
+
props: Dict[str, str],
|
|
749
|
+
client: bool,
|
|
750
|
+
typescript: bool
|
|
751
|
+
) -> str:
|
|
752
|
+
"""Generate component code"""
|
|
753
|
+
use_client = '"use client"\n\n' if client else ''
|
|
754
|
+
|
|
755
|
+
if typescript and props:
|
|
756
|
+
interface = f"interface {name}Props {{\n"
|
|
757
|
+
for prop_name, prop_type in props.items():
|
|
758
|
+
interface += f" {prop_name}: {prop_type}\n"
|
|
759
|
+
interface += "}\n\n"
|
|
760
|
+
else:
|
|
761
|
+
interface = ""
|
|
762
|
+
|
|
763
|
+
props_destructure = ", ".join(props.keys()) if props else ""
|
|
764
|
+
props_type = f": {name}Props" if typescript and props else ""
|
|
765
|
+
|
|
766
|
+
return f'''{use_client}{interface}export default function {name}({{ {props_destructure} }}{props_type}) {{
|
|
767
|
+
return (
|
|
768
|
+
<div>
|
|
769
|
+
<h2>{name} Component</h2>
|
|
770
|
+
</div>
|
|
771
|
+
)
|
|
772
|
+
}}
|
|
773
|
+
'''
|
|
774
|
+
|
|
775
|
+
def _generate_api_route_code(self, path: str, methods: List[str], typescript: bool) -> str:
|
|
776
|
+
"""Generate API route code"""
|
|
777
|
+
code = ""
|
|
778
|
+
|
|
779
|
+
if typescript:
|
|
780
|
+
code += "import { NextRequest, NextResponse } from 'next/server'\n\n"
|
|
781
|
+
|
|
782
|
+
for method in methods:
|
|
783
|
+
method_upper = method.upper()
|
|
784
|
+
if method_upper == "GET":
|
|
785
|
+
code += f'''export async function GET() {{
|
|
786
|
+
return Response.json({{ message: 'GET /{path}' }})
|
|
787
|
+
}}
|
|
788
|
+
|
|
789
|
+
'''
|
|
790
|
+
elif method_upper == "POST":
|
|
791
|
+
code += f'''export async function POST(request{': NextRequest' if typescript else ''}) {{
|
|
792
|
+
const data = await request.json()
|
|
793
|
+
return Response.json({{ message: 'POST /{path}', data }})
|
|
794
|
+
}}
|
|
795
|
+
|
|
796
|
+
'''
|
|
797
|
+
elif method_upper == "PUT":
|
|
798
|
+
code += f'''export async function PUT(request{': NextRequest' if typescript else ''}) {{
|
|
799
|
+
const data = await request.json()
|
|
800
|
+
return Response.json({{ message: 'PUT /{path}', data }})
|
|
801
|
+
}}
|
|
802
|
+
|
|
803
|
+
'''
|
|
804
|
+
elif method_upper == "DELETE":
|
|
805
|
+
code += f'''export async function DELETE() {{
|
|
806
|
+
return Response.json({{ message: 'DELETE /{path}' }})
|
|
807
|
+
}}
|
|
808
|
+
|
|
809
|
+
'''
|
|
810
|
+
|
|
811
|
+
return code.strip() + '\n'
|
|
812
|
+
|
|
813
|
+
def _generate_layout_code(self, name: str, typescript: bool) -> str:
|
|
814
|
+
"""Generate layout code"""
|
|
815
|
+
props_type = ": { children: React.ReactNode }" if typescript else ""
|
|
816
|
+
|
|
817
|
+
return f'''export default function {name}({{
|
|
818
|
+
children,
|
|
819
|
+
}}{props_type}) {{
|
|
820
|
+
return (
|
|
821
|
+
<div>
|
|
822
|
+
<header className="p-4 border-b">
|
|
823
|
+
<nav>Navigation</nav>
|
|
824
|
+
</header>
|
|
825
|
+
<main>{{children}}</main>
|
|
826
|
+
<footer className="p-4 border-t">
|
|
827
|
+
Footer
|
|
828
|
+
</footer>
|
|
829
|
+
</div>
|
|
830
|
+
)
|
|
831
|
+
}}
|
|
832
|
+
'''
|
|
833
|
+
|
|
834
|
+
def can_handle(self, request: str) -> float:
|
|
835
|
+
"""Check if request is Next.js-related"""
|
|
836
|
+
request_lower = request.lower()
|
|
837
|
+
|
|
838
|
+
high_conf = ["nextjs", "next.js", "next js", "react component", "app router"]
|
|
839
|
+
for kw in high_conf:
|
|
840
|
+
if kw in request_lower:
|
|
841
|
+
return 0.9
|
|
842
|
+
|
|
843
|
+
med_conf = ["react", "component", "page", "layout", "typescript"]
|
|
844
|
+
for kw in med_conf:
|
|
845
|
+
if kw in request_lower:
|
|
846
|
+
return 0.5
|
|
847
|
+
|
|
848
|
+
return super().can_handle(request)
|
|
849
|
+
|
|
850
|
+
def handle_request(self, request: str, **kwargs) -> Optional[ActionResult]:
|
|
851
|
+
"""Handle a natural language request"""
|
|
852
|
+
request_lower = request.lower()
|
|
853
|
+
|
|
854
|
+
if "scaffold" in request_lower or "new project" in request_lower:
|
|
855
|
+
words = request.split()
|
|
856
|
+
name = "my_app"
|
|
857
|
+
for i, word in enumerate(words):
|
|
858
|
+
if word.lower() in ["project", "called", "named"]:
|
|
859
|
+
if i + 1 < len(words):
|
|
860
|
+
name = words[i + 1].strip("'\"")
|
|
861
|
+
break
|
|
862
|
+
return self.scaffold_project(name=name)
|
|
863
|
+
|
|
864
|
+
if "analyze" in request_lower:
|
|
865
|
+
return self.analyze_project()
|
|
866
|
+
|
|
867
|
+
return None
|