ntc-dev-kit 1.2.1__tar.gz → 1.3.0__tar.gz

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 (211) hide show
  1. {ntc_dev_kit-1.2.1/ntc_dev_kit.egg-info → ntc_dev_kit-1.3.0}/PKG-INFO +81 -2
  2. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/README.md +79 -1
  3. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/__init__.py +1 -1
  4. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/cli_enhanced.py +61 -26
  5. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/main.py +12 -0
  6. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/utils/prompts.py +179 -0
  7. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/wizard.py +33 -15
  8. ntc_dev_kit-1.3.0/ntc/workspace/__init__.py +2 -0
  9. ntc_dev_kit-1.3.0/ntc/workspace/clean.py +214 -0
  10. ntc_dev_kit-1.3.0/ntc/workspace/config.py +342 -0
  11. ntc_dev_kit-1.3.0/ntc/workspace/create.py +449 -0
  12. ntc_dev_kit-1.3.0/ntc/workspace/processes.py +307 -0
  13. ntc_dev_kit-1.3.0/ntc/workspace/screen.py +217 -0
  14. ntc_dev_kit-1.3.0/ntc/workspace/setup.py +108 -0
  15. ntc_dev_kit-1.3.0/ntc/workspace/start.py +112 -0
  16. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0/ntc_dev_kit.egg-info}/PKG-INFO +81 -2
  17. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc_dev_kit.egg-info/SOURCES.txt +10 -1
  18. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc_dev_kit.egg-info/requires.txt +3 -0
  19. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/pyproject.toml +2 -1
  20. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/setup.py +2 -1
  21. ntc_dev_kit-1.3.0/tests/test_workspace.py +96 -0
  22. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/LICENSE +0 -0
  23. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/MANIFEST.in +0 -0
  24. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/app_creator.py +0 -0
  25. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/cli.py +0 -0
  26. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/composer/__init__.py +0 -0
  27. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/composer/main.py +0 -0
  28. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/deployment/__init__.py +0 -0
  29. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/main.py +0 -0
  30. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/__init__.py +0 -0
  31. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/auth_app/__init__.py +0 -0
  32. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/auth_app/admin.py +0 -0
  33. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/auth_app/apps.py +0 -0
  34. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/auth_app/migrations/0001_initial.py +0 -0
  35. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/auth_app/models.py +0 -0
  36. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/auth_app/serializers.py +0 -0
  37. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/auth_app/tasks.py +0 -0
  38. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/auth_app/templates/authentication/password_reset.html +0 -0
  39. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/auth_app/templates/authentication/password_reset.txt +0 -0
  40. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/auth_app/tests.py +0 -0
  41. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/auth_app/urls.py +0 -0
  42. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/auth_app/views.py +0 -0
  43. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/__init__.py +0 -0
  44. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/drf_structure.md +0 -0
  45. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/structure.json +0 -0
  46. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/.entry/start_core_backend.sh +0 -0
  47. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/.entry/start_tasks_backend.sh +0 -0
  48. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/CLAUDE.md +0 -0
  49. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/README.md +0 -0
  50. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/apps/app_admin.py +0 -0
  51. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/apps/app_apps.py +0 -0
  52. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/apps/app_models.py +0 -0
  53. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/apps/app_urls.py +0 -0
  54. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/apps/app_views.py +0 -0
  55. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/apps/common_apps.py +0 -0
  56. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/apps/common_checks.py +0 -0
  57. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/apps/common_models.py +0 -0
  58. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/ci/github_ci.yml +0 -0
  59. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/ci/pre_commit.yaml +0 -0
  60. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/config/gunicorn_config.py +0 -0
  61. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/config/http.conf +0 -0
  62. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/core/exceptions/handler.py +0 -0
  63. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/core/formatter.py +0 -0
  64. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/core/health.py +0 -0
  65. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/core/logger.py +0 -0
  66. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/core/pagination.py +0 -0
  67. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/core/renderers.py +0 -0
  68. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/core/response_wrapper.py +0 -0
  69. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/core/schema.py +0 -0
  70. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/core/services/azure/email/backend.py +0 -0
  71. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/core/services/azure/email/client.py +0 -0
  72. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/core/services/azure/email/notifier.py +0 -0
  73. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/core/services/object_storage.py +0 -0
  74. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/core/tests.py +0 -0
  75. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/core/utils/request.py +0 -0
  76. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/django_project/asgi.py +0 -0
  77. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/django_project/celery.py +0 -0
  78. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/django_project/dev_settings.py +0 -0
  79. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/django_project/django_settings.txt +0 -0
  80. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/django_project/prod_settings.py +0 -0
  81. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/django_project/project_init.py +0 -0
  82. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/django_project/schema.py +0 -0
  83. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/django_project/test_settings.py +0 -0
  84. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/django_project/urls.py +0 -0
  85. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/django_project/wsgi.py +0 -0
  86. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/docker/Dockerfile +0 -0
  87. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/docker/captain-definition +0 -0
  88. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/docker/docker-compose.yml +0 -0
  89. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/knowledge/knowledge_description.md +0 -0
  90. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/knowledge/knowledge_design_pattern.md +0 -0
  91. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/knowledge/knowledge_structure.md +0 -0
  92. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/root/README.md +0 -0
  93. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/root/django_env.txt +0 -0
  94. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/root/django_requirements.txt +0 -0
  95. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/root/django_requirements_dev.txt +0 -0
  96. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/root/dockerignore.txt +0 -0
  97. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/root/gitattributes.txt +0 -0
  98. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/root/gitignore.txt +0 -0
  99. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/root/manage.py +0 -0
  100. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/root/pytest_ini.txt +0 -0
  101. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/drf_structure/templates/root/ruff_toml.txt +0 -0
  102. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/.gitignore +0 -0
  103. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/.knowledge/knowledge_description.md +0 -0
  104. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/.knowledge/knowledge_design_pattern.md +0 -0
  105. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/.knowledge/knowledge_structure.md +0 -0
  106. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/CLAUDE.md +0 -0
  107. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/__init__.py +0 -0
  108. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/analysis_options.yaml +0 -0
  109. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/lib/core/config/app_config.dart +0 -0
  110. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/lib/core/config/environment.dart +0 -0
  111. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/lib/core/constants/app_constants.dart +0 -0
  112. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/lib/core/network/dio_client.dart +0 -0
  113. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/lib/features/home/presentation/home_screen.dart +0 -0
  114. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/lib/l10n/app_localizations.dart +0 -0
  115. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/lib/main.dart +0 -0
  116. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/lib/services/api_service.dart +0 -0
  117. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/lib/shared/localization/locale_provider.dart +0 -0
  118. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/lib/shared/theme/app_theme.dart +0 -0
  119. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/lib/shared/theme/theme_provider.dart +0 -0
  120. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/main.py +0 -0
  121. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/pubspec.yaml +0 -0
  122. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/flutter/structure.json +0 -0
  123. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/.env.example +0 -0
  124. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/.gitignore +0 -0
  125. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/CLAUDE.md +0 -0
  126. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/README.md +0 -0
  127. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/__init__.py +0 -0
  128. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/eslint.config.mjs +0 -0
  129. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/knowledge/knowledge_description.md +0 -0
  130. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/knowledge/knowledge_design_pattern.md +0 -0
  131. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/knowledge/knowledge_structure.md +0 -0
  132. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/main.py +0 -0
  133. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/next.config.js +0 -0
  134. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/package.json +0 -0
  135. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/src/app/globals.css +0 -0
  136. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/src/app/layout.tsx +0 -0
  137. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/src/app/page.tsx +0 -0
  138. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/src/components/Header.tsx +0 -0
  139. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/src/services/api.ts +0 -0
  140. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/src/services/server.ts +0 -0
  141. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/src/utils/constants.ts +0 -0
  142. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/structure.json +0 -0
  143. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/nextjs/tsconfig.json +0 -0
  144. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/.env.example +0 -0
  145. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/.gitignore +0 -0
  146. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/CLAUDE.md +0 -0
  147. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/README.md +0 -0
  148. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/__init__.py +0 -0
  149. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/eslint.config.js +0 -0
  150. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/index.html +0 -0
  151. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/knowledge/knowledge_description.md +0 -0
  152. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/knowledge/knowledge_design_pattern.md +0 -0
  153. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/knowledge/knowledge_structure.md +0 -0
  154. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/main.py +0 -0
  155. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/package.json +0 -0
  156. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/src/App.tsx +0 -0
  157. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/src/components/ProtectedRoute.tsx +0 -0
  158. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/src/components/Sidebar.tsx +0 -0
  159. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/src/components/TopNav.tsx +0 -0
  160. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/src/context/AuthContext.tsx +0 -0
  161. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/src/context/ThemeContext.tsx +0 -0
  162. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/src/context/WorkspaceContext.tsx +0 -0
  163. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/src/hooks/useApi.ts +0 -0
  164. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/src/index.css +0 -0
  165. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/src/layouts/AuthLayout.tsx +0 -0
  166. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/src/layouts/DashboardLayout.tsx +0 -0
  167. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/src/main.tsx +0 -0
  168. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/src/pages/DashboardPage.tsx +0 -0
  169. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/src/pages/auth/LoginPage.tsx +0 -0
  170. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/src/services/api.service.ts +0 -0
  171. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/src/services/base.service.ts +0 -0
  172. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/src/services/token.ts +0 -0
  173. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/src/vite-env.d.ts +0 -0
  174. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/structure.json +0 -0
  175. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/tsconfig.app.json +0 -0
  176. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/tsconfig.json +0 -0
  177. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/tsconfig.node.json +0 -0
  178. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_admin/vite.config.ts +0 -0
  179. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/App.css +0 -0
  180. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/CLAUDE.md +0 -0
  181. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/__init__.py +0 -0
  182. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/index.css +0 -0
  183. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/index.html +0 -0
  184. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/knowledge/knowledge_description.md +0 -0
  185. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/knowledge/knowledge_design_pattern.md +0 -0
  186. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/knowledge/knowledge_structure.md +0 -0
  187. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/main.py +0 -0
  188. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/package.json +0 -0
  189. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/src/App.tsx +0 -0
  190. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/src/components/Header.tsx +0 -0
  191. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/src/main.tsx +0 -0
  192. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/src/services/api.ts +0 -0
  193. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/src/utils/constants.ts +0 -0
  194. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/src/vite-env.d.ts +0 -0
  195. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/structure.json +0 -0
  196. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/tsconfig.app.json +0 -0
  197. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/tsconfig.json +0 -0
  198. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/tsconfig.node.json +0 -0
  199. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/templates/react_structure/vite.config.ts +0 -0
  200. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/ui/__init__.py +0 -0
  201. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/ui/app.py +0 -0
  202. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/utils/__init__.py +0 -0
  203. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/utils/environment.py +0 -0
  204. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/utils/output.py +0 -0
  205. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/utils/shell.py +0 -0
  206. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc/utils/validation.py +0 -0
  207. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc_dev_kit.egg-info/dependency_links.txt +0 -0
  208. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc_dev_kit.egg-info/entry_points.txt +0 -0
  209. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/ntc_dev_kit.egg-info/top_level.txt +0 -0
  210. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/requirements.txt +0 -0
  211. {ntc_dev_kit-1.2.1 → ntc_dev_kit-1.3.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ntc-dev-kit
3
- Version: 1.2.1
3
+ Version: 1.3.0
4
4
  Summary: Nexent Toolkit Console - A CLI tool for scaffolding Django REST Framework, React, Next.js, and Flutter projects
5
5
  Home-page: https://github.com/nexent/ntc
6
6
  Author: Nexent
@@ -27,6 +27,7 @@ License-File: LICENSE
27
27
  Requires-Dist: colorama>=0.4.6
28
28
  Requires-Dist: pyyaml>=6.0
29
29
  Requires-Dist: textual>=0.47.0
30
+ Requires-Dist: tomli>=2.0; python_version < "3.11"
30
31
  Provides-Extra: dev
31
32
  Requires-Dist: build>=1.0; extra == "dev"
32
33
  Requires-Dist: twine>=4.0; extra == "dev"
@@ -121,7 +122,85 @@ ntc init -t nextjs --name site --install --yes
121
122
  | `--with-auth` / `--no-auth` | Django: include the sign-in app (custom User model, JWT, migration included) |
122
123
  | `--env` | Django: `venv`, `conda`, `existing` (the active virtualenv) or `skip` |
123
124
  | `--install` / `--no-install` | Others: run `npm install` / `flutter pub get` |
124
- | `--yes`, `-y` | Ask nothing; needs `--name` and `--technology` |
125
+ | `--apps` | A workspace instead of one app: `backend,website,dashboard,admin,mobile` (any of them) |
126
+ | `--git` | Workspace: `per-app` (default), `single` or `none` |
127
+ | `--yes`, `-y` | Ask nothing; needs `--name` and `--technology` (or `--apps`) |
128
+
129
+ ### Workspaces: several apps at once
130
+
131
+ Tick more than one app at **What are you building?** (Space ticks) - say a backend, a website and
132
+ an admin dashboard - and they're created side by side in one folder, already connected:
133
+
134
+ - each app gets its own port, the frontends point at the backend's API, and the backend allows
135
+ the frontends' origins (CORS);
136
+ - dependencies install for all apps at once, one line each;
137
+ - git: a repository per app (plus a small one at the root for `ntc.toml`), one for everything,
138
+ or none;
139
+ - `ntc.toml` at the root says how each app is set up and run.
140
+
141
+ ```
142
+ kaya/
143
+ ├── ntc.toml # the apps, their ports, setup and run commands, env
144
+ ├── README.md
145
+ ├── backend/ # Django REST API http://127.0.0.1:8000
146
+ ├── website/ # Next.js http://127.0.0.1:3000
147
+ └── admin/ # React admin http://127.0.0.1:5173
148
+ ```
149
+
150
+ Then, from anywhere inside the workspace:
151
+
152
+ ```bash
153
+ ntc start # every app in this terminal: a tab each plus "All"
154
+ ntc start backend admin # just these (and what they need)
155
+ ntc start --plain # interleaved output instead (CI, logs)
156
+ ntc setup # environments, .env files, dependencies - after a clone or a deep clean
157
+ ntc clean # build output and caches; shows the space freed
158
+ ntc clean --deep # also node_modules and Python environments
159
+ ntc add # another app, wired in like the rest
160
+ ```
161
+
162
+ In `ntc start`: **1-9** (or the tabs) switch app, **0** shows all, **r** restarts, **s** stops or
163
+ starts, **o** opens it in the browser, **i** types into it (Flutter's `r` hot reload), **q** quits
164
+ and stops everything. Apps wait for the ones they `need` (the backend) before starting, and a port
165
+ that's already taken is reported instead of crashing.
166
+
167
+ `ntc clean` only removes what the tools rebuild (`.next`, `dist`, `__pycache__`, caches; with
168
+ `--deep` also `node_modules` and `env`), never anything git tracks, and skips apps that are running.
169
+ `--dry-run` shows what would go.
170
+
171
+ `ntc.toml` is plain TOML and meant to be edited and committed (no secrets in it - those stay in
172
+ each app's `.env`):
173
+
174
+ ```toml
175
+ name = "kaya"
176
+
177
+ [apps.backend]
178
+ kind = "django"
179
+ path = "backend"
180
+ port = 8000
181
+ python = "env"
182
+ setup = ["{python} -m pip install -r requirements-dev.txt", "{python} manage.py migrate"]
183
+ run = "{python} manage.py runserver 127.0.0.1:{port}"
184
+ # also = { worker = "{python} -m celery -A kaya worker -B -l INFO" }
185
+
186
+ [apps.backend.env]
187
+ CORS_ALLOWED_ORIGINS = "{frontends.origins}"
188
+
189
+ [apps.website]
190
+ kind = "nextjs"
191
+ path = "website"
192
+ port = 3000
193
+ needs = ["backend"]
194
+ run = "npm run dev -- --port {port}"
195
+ repo = "git@github.com:you/website.git" # `ntc setup` clones it when the folder is missing
196
+
197
+ [apps.website.env]
198
+ NEXT_PUBLIC_API_BASE_URL = "{backend.url}/api/v1"
199
+ ```
200
+
201
+ Placeholders: `{port}`, `{url}`, `{python}` (this app's), `{<app>.port}`, `{<app>.url}`,
202
+ `{frontends.origins}`. Commands are one program with its arguments (no `&&`), so they run the
203
+ same on Windows and Unix.
125
204
 
126
205
  ### Technology Options
127
206
 
@@ -81,7 +81,85 @@ ntc init -t nextjs --name site --install --yes
81
81
  | `--with-auth` / `--no-auth` | Django: include the sign-in app (custom User model, JWT, migration included) |
82
82
  | `--env` | Django: `venv`, `conda`, `existing` (the active virtualenv) or `skip` |
83
83
  | `--install` / `--no-install` | Others: run `npm install` / `flutter pub get` |
84
- | `--yes`, `-y` | Ask nothing; needs `--name` and `--technology` |
84
+ | `--apps` | A workspace instead of one app: `backend,website,dashboard,admin,mobile` (any of them) |
85
+ | `--git` | Workspace: `per-app` (default), `single` or `none` |
86
+ | `--yes`, `-y` | Ask nothing; needs `--name` and `--technology` (or `--apps`) |
87
+
88
+ ### Workspaces: several apps at once
89
+
90
+ Tick more than one app at **What are you building?** (Space ticks) - say a backend, a website and
91
+ an admin dashboard - and they're created side by side in one folder, already connected:
92
+
93
+ - each app gets its own port, the frontends point at the backend's API, and the backend allows
94
+ the frontends' origins (CORS);
95
+ - dependencies install for all apps at once, one line each;
96
+ - git: a repository per app (plus a small one at the root for `ntc.toml`), one for everything,
97
+ or none;
98
+ - `ntc.toml` at the root says how each app is set up and run.
99
+
100
+ ```
101
+ kaya/
102
+ ├── ntc.toml # the apps, their ports, setup and run commands, env
103
+ ├── README.md
104
+ ├── backend/ # Django REST API http://127.0.0.1:8000
105
+ ├── website/ # Next.js http://127.0.0.1:3000
106
+ └── admin/ # React admin http://127.0.0.1:5173
107
+ ```
108
+
109
+ Then, from anywhere inside the workspace:
110
+
111
+ ```bash
112
+ ntc start # every app in this terminal: a tab each plus "All"
113
+ ntc start backend admin # just these (and what they need)
114
+ ntc start --plain # interleaved output instead (CI, logs)
115
+ ntc setup # environments, .env files, dependencies - after a clone or a deep clean
116
+ ntc clean # build output and caches; shows the space freed
117
+ ntc clean --deep # also node_modules and Python environments
118
+ ntc add # another app, wired in like the rest
119
+ ```
120
+
121
+ In `ntc start`: **1-9** (or the tabs) switch app, **0** shows all, **r** restarts, **s** stops or
122
+ starts, **o** opens it in the browser, **i** types into it (Flutter's `r` hot reload), **q** quits
123
+ and stops everything. Apps wait for the ones they `need` (the backend) before starting, and a port
124
+ that's already taken is reported instead of crashing.
125
+
126
+ `ntc clean` only removes what the tools rebuild (`.next`, `dist`, `__pycache__`, caches; with
127
+ `--deep` also `node_modules` and `env`), never anything git tracks, and skips apps that are running.
128
+ `--dry-run` shows what would go.
129
+
130
+ `ntc.toml` is plain TOML and meant to be edited and committed (no secrets in it - those stay in
131
+ each app's `.env`):
132
+
133
+ ```toml
134
+ name = "kaya"
135
+
136
+ [apps.backend]
137
+ kind = "django"
138
+ path = "backend"
139
+ port = 8000
140
+ python = "env"
141
+ setup = ["{python} -m pip install -r requirements-dev.txt", "{python} manage.py migrate"]
142
+ run = "{python} manage.py runserver 127.0.0.1:{port}"
143
+ # also = { worker = "{python} -m celery -A kaya worker -B -l INFO" }
144
+
145
+ [apps.backend.env]
146
+ CORS_ALLOWED_ORIGINS = "{frontends.origins}"
147
+
148
+ [apps.website]
149
+ kind = "nextjs"
150
+ path = "website"
151
+ port = 3000
152
+ needs = ["backend"]
153
+ run = "npm run dev -- --port {port}"
154
+ repo = "git@github.com:you/website.git" # `ntc setup` clones it when the folder is missing
155
+
156
+ [apps.website.env]
157
+ NEXT_PUBLIC_API_BASE_URL = "{backend.url}/api/v1"
158
+ ```
159
+
160
+ Placeholders: `{port}`, `{url}`, `{python}` (this app's), `{<app>.port}`, `{<app>.url}`,
161
+ `{frontends.origins}`. Commands are one program with its arguments (no `&&`), so they run the
162
+ same on Windows and Unix.
85
163
 
86
164
  ### Technology Options
87
165
 
@@ -1,5 +1,5 @@
1
1
  """NTC - Nexent Toolkit Console."""
2
2
 
3
- __version__ = "1.2.1"
3
+ __version__ = "1.3.0"
4
4
  __author__ = "Nexent"
5
5
  __license__ = "MIT"
@@ -284,8 +284,10 @@ Examples:
284
284
  ntc init -t django --name shop-api --with-auth --env venv --yes
285
285
  # No questions at all (scripts, CI, AI agents)
286
286
  ntc init -t nextjs --name site --install --yes
287
- ntc compose myproject # Compose full-stack project with multiple components
288
- ntc start # Start all services from ntc.yml configuration
287
+ ntc init --name shop --apps backend,website,admin --yes
288
+ # A workspace: several apps wired together, plus ntc.toml
289
+ ntc start # Run the workspace's apps in this terminal
290
+ ntc setup / ntc clean / ntc add
289
291
  ntc create app myapp # Create new Django app
290
292
  ntc drf init path name # Legacy Django REST Framework initialization
291
293
  """
@@ -321,7 +323,11 @@ Examples:
321
323
  help="Next.js/React/Flutter: don't install dependencies")
322
324
  init_parser.add_argument("--yes", "-y", action="store_true",
323
325
  help="Don't ask: use the options given and defaults for the rest "
324
- "(requires --name and --technology)")
326
+ "(requires --name and --technology or --apps)")
327
+ init_parser.add_argument("--apps", help="A workspace of several apps, comma-separated: "
328
+ "backend, website, dashboard, admin, mobile")
329
+ init_parser.add_argument("--git", choices=["per-app", "single", "none"],
330
+ help="Workspace: a repository per app (default), one for everything, or none")
325
331
 
326
332
  # Create command for apps
327
333
  create_parser = subparsers.add_parser("create", help="Create new components")
@@ -345,13 +351,32 @@ Examples:
345
351
  drf_init_parser.add_argument("name", help="Project name")
346
352
 
347
353
  # Composer command for full-stack project composition
348
- composer_parser = subparsers.add_parser("compose", help="Compose a full-stack project with multiple components")
349
- composer_parser.add_argument("project_name", help="Name of the project")
350
- composer_parser.add_argument("--path", help="Base path to create the project (default: current directory)")
354
+ subparsers.add_parser("compose", help="Replaced by `ntc init` (pick several apps)")
355
+ add_parser = subparsers.add_parser("add", help="Add apps to the workspace")
356
+ add_parser.add_argument("apps", nargs="*", help="backend, website, dashboard, admin, mobile")
357
+ add_parser.add_argument("--name", help="Folder name (when adding one app)")
358
+ add_parser.add_argument("--env", choices=["venv", "conda", "existing", "skip"], help="Backend: Python environment")
359
+ add_auth = add_parser.add_mutually_exclusive_group()
360
+ add_auth.add_argument("--with-auth", dest="with_auth", action="store_true", default=None)
361
+ add_auth.add_argument("--no-auth", dest="with_auth", action="store_false")
362
+ add_install = add_parser.add_mutually_exclusive_group()
363
+ add_install.add_argument("--install", dest="install", action="store_true", default=None)
364
+ add_install.add_argument("--no-install", dest="install", action="store_false")
365
+ add_parser.add_argument("--yes", "-y", action="store_true", help="Don't ask")
366
+ setup_parser = subparsers.add_parser("setup", help="Install the workspace's apps (after a clone or a deep clean)")
367
+ setup_parser.add_argument("apps", nargs="*", help="Only these apps")
368
+ clean_parser = subparsers.add_parser("clean", help="Delete build output and caches; say how much space it freed")
369
+ clean_parser.add_argument("apps", nargs="*", help="Only these apps")
370
+ clean_parser.add_argument("--deep", action="store_true",
371
+ help="Also dependencies: node_modules, Python environments (`ntc setup` reinstalls)")
372
+ clean_parser.add_argument("--dry-run", action="store_true", help="Show what would go, delete nothing")
373
+ clean_parser.add_argument("--yes", "-y", action="store_true", help="Don't ask before deleting")
351
374
 
352
375
  # Start services command
353
- start_parser = subparsers.add_parser("start", help="Start all services defined in ntc.yml")
354
- start_parser.add_argument("--path", help="Path to project root directory (default: current directory)")
376
+ start_parser = subparsers.add_parser("start", help="Run the workspace's apps together in this terminal")
377
+ start_parser.add_argument("apps", nargs="*", help="Only these apps (and the ones they need)")
378
+ start_parser.add_argument("--plain", action="store_true",
379
+ help="Interleaved output instead of the full-screen view (CI, logs)")
355
380
 
356
381
  # Doctor command for setup diagnostics
357
382
  doctor_parser = subparsers.add_parser("doctor", help="Check setup and fix common issues")
@@ -364,12 +389,20 @@ Examples:
364
389
  if args.command == "init":
365
390
  technology = args.technology or args.frontend or args.backend
366
391
  technology = {"drf": "django", "laravel": "django"}.get(technology, technology)
367
- if args.yes and not (args.name and technology):
368
- print_error("--yes needs --name and --technology, so there is nothing left to ask.")
392
+ apps = [a.strip() for a in args.apps.split(",") if a.strip()] if args.apps else None
393
+ if apps and technology:
394
+ print_error("Give either --technology (one app) or --apps (a workspace), not both.")
369
395
  sys.exit(2)
396
+ if args.yes and not (args.name and (technology or apps)):
397
+ print_error("--yes needs --name and --technology (or --apps), so there is nothing left to ask.")
398
+ sys.exit(2)
399
+ if apps and len(apps) == 1:
400
+ from ntc.workspace.config import ROLE_KINDS
401
+ technology, apps = ROLE_KINDS.get(apps[0], apps[0]), None
370
402
  from ntc.wizard import run as run_wizard
371
403
  created = run_wizard(technology=technology, name=args.name, path=args.path, with_auth=args.with_auth,
372
- env_choice=args.env, install=args.install, assume_yes=args.yes)
404
+ env_choice=args.env, install=args.install, assume_yes=args.yes,
405
+ apps=apps, git=args.git)
373
406
  sys.exit(0 if created else 1)
374
407
  elif args.command == "create" and args.create_type == "app":
375
408
  if not is_valid_project_name(args.app_name):
@@ -385,22 +418,23 @@ Examples:
385
418
  from ntc.templates.drf_structure.main import init_drf_project
386
419
  init_drf_project(args.path, args.name)
387
420
  elif args.command == "compose":
388
- # Composer for full-stack project composition
389
- if not is_valid_project_name(args.project_name):
390
- print_error("Project name may only contain letters, numbers, underscores, and hyphens!")
391
- sys.exit(1)
392
- from ntc.composer.main import compose_project
393
- if args.path:
394
- compose_project(args.project_name, args.path)
395
- else:
396
- compose_project(args.project_name)
421
+ print_info("`ntc compose` is now part of `ntc init`: pick several apps (Space ticks them) and they're "
422
+ "created together as a workspace, with an ntc.toml for `ntc start`.")
423
+ sys.exit(2)
397
424
  elif args.command == "start":
398
- # Start services from ntc.yml configuration
399
- from ntc.composer.main import start_services
400
- if args.path:
401
- start_services(args.path)
402
- else:
403
- start_services()
425
+ from ntc.workspace.start import run as run_start
426
+ sys.exit(0 if run_start(args.apps, plain=args.plain) else 1)
427
+ elif args.command == "setup":
428
+ from ntc.workspace.setup import run as run_setup
429
+ sys.exit(0 if run_setup(args.apps) else 1)
430
+ elif args.command == "clean":
431
+ from ntc.workspace.clean import run as run_clean
432
+ sys.exit(0 if run_clean(args.apps, deep=args.deep, assume_yes=args.yes, dry_run=args.dry_run) else 1)
433
+ elif args.command == "add":
434
+ from ntc.workspace.create import run_add
435
+ env_choice = {"existing": "active"}.get(args.env, args.env)
436
+ sys.exit(0 if run_add(args.apps, name=args.name, with_auth=args.with_auth, env_choice=env_choice,
437
+ install=args.install, assume_yes=args.yes) else 1)
404
438
  elif args.command == "doctor":
405
439
  run_doctor()
406
440
  elif args.command == "tui":
@@ -412,6 +446,7 @@ Examples:
412
446
  print(f"\n {Fore.LIGHTBLACK_EX}Quick Start:{Style.RESET_ALL}")
413
447
  print(f" {Fore.WHITE}ntc tui{Style.RESET_ALL} {Fore.LIGHTBLACK_EX}interactive terminal UI{Style.RESET_ALL}")
414
448
  print(f" {Fore.WHITE}ntc init{Style.RESET_ALL} {Fore.LIGHTBLACK_EX}interactive setup{Style.RESET_ALL}")
449
+ print(f" {Fore.WHITE}ntc start{Style.RESET_ALL} {Fore.LIGHTBLACK_EX}run a workspace's apps{Style.RESET_ALL}")
415
450
  print(f" {Fore.WHITE}ntc init -t django{Style.RESET_ALL} {Fore.LIGHTBLACK_EX}Django project{Style.RESET_ALL}")
416
451
  print(f" {Fore.WHITE}ntc doctor{Style.RESET_ALL} {Fore.LIGHTBLACK_EX}check setup{Style.RESET_ALL}")
417
452
  print()
@@ -373,6 +373,18 @@ def _render_project(project_root, project_name, original_name, with_auth):
373
373
  return True
374
374
 
375
375
 
376
+ def write_env_file(project_root, project_name, original_name=None):
377
+ """Write the project's .env (development defaults, a new SECRET_KEY). It's gitignored, so a
378
+ fresh clone has none; `ntc setup` uses this to recreate it."""
379
+ structure_file = os.path.join(os.path.dirname(__file__), "structure.json")
380
+ with open(structure_file, "r", encoding="utf-8") as f:
381
+ template = json.load(f)[".env"]["template"]
382
+ variables = {"project_name": project_name, "original_name": original_name or project_name,
383
+ "secret_key": secrets.token_urlsafe(50)}
384
+ with open(os.path.join(project_root, ".env"), "w", encoding="utf-8") as f:
385
+ f.write(get_template_content(template, variables))
386
+
387
+
376
388
  def init_drf_project(base_path, project_name, with_auth=False, env_choice=None):
377
389
  """Create a DRF project in base_path/<sanitized name> and set up its environment.
378
390
 
@@ -189,6 +189,82 @@ def text(question, default=None, validate=None):
189
189
  return value
190
190
 
191
191
 
192
+ def multiselect(question, options, default=(), hint=None):
193
+ """Pick any of options: (label, value) or (label, value, description) tuples.
194
+
195
+ ↑/↓ move, Space (or the option's number) ticks/unticks, Enter confirms, Esc cancels.
196
+ Returns the ticked values in option order. Without a terminal: the defaults.
197
+ """
198
+ options = [opt if len(opt) == 3 else (opt[0], opt[1], "") for opt in options]
199
+ ticked = {value for _, value, _ in options if value in default}
200
+ if not interactive():
201
+ chosen = [value for _, value, _ in options if value in ticked]
202
+ answered(question, ", ".join(label for label, value, _ in options if value in ticked) or "none")
203
+ return chosen
204
+
205
+ cursor = 0
206
+ error = ""
207
+ lines = len(options) + 2
208
+
209
+ def draw(first=False):
210
+ width = _width()
211
+ if not first:
212
+ _write(_UP.format(lines))
213
+ hint_text = hint or "Space to tick, Enter to confirm"
214
+ if len(question) + len(hint_text) + 3 > width:
215
+ hint_text = ""
216
+ _write(f"{_CLEAR_LINE}{QUESTION} {Style.BRIGHT}{_truncate(question, width - 2)}{RESET} "
217
+ f"{DIM}{hint_text}{RESET}\n")
218
+ for i, (label, value, description) in enumerate(options):
219
+ pointer = POINTER if i == cursor else " "
220
+ box = f"{Fore.GREEN}◉{RESET}" if value in ticked else f"{DIM}◯{RESET}"
221
+ room = width - 8 - len(str(i + 1)) # " ❯ ◉ 1 "
222
+ if len(label) + 2 + len(description) > room:
223
+ description = ""
224
+ label = _truncate(label, room)
225
+ text = f"{Fore.CYAN}{Style.BRIGHT}{label}{RESET}" if i == cursor else label
226
+ extra = f" {DIM}{description}{RESET}" if description else ""
227
+ _write(f"{_CLEAR_LINE} {pointer} {box} {DIM}{i + 1}{RESET} {text}{extra}\n")
228
+ _write(f"{_CLEAR_LINE} {Fore.RED}{_truncate(error, width - 2)}{RESET}\n" if error else f"{_CLEAR_LINE}\n")
229
+
230
+ def collapse():
231
+ _write(_UP.format(lines))
232
+ for _ in range(lines):
233
+ _write(f"{_CLEAR_LINE}\n")
234
+ _write(_UP.format(lines))
235
+
236
+ draw(first=True)
237
+ try:
238
+ while True:
239
+ key = _read_key()
240
+ error = ""
241
+ if key == "up":
242
+ cursor = (cursor - 1) % len(options)
243
+ elif key == "down":
244
+ cursor = (cursor + 1) % len(options)
245
+ elif key == " " or (key.isdigit() and 1 <= int(key) <= len(options)):
246
+ if key != " ":
247
+ cursor = int(key) - 1
248
+ value = options[cursor][1]
249
+ ticked.symmetric_difference_update({value})
250
+ elif key == "enter":
251
+ if ticked:
252
+ break
253
+ error = "Tick at least one (Space)"
254
+ elif key == "escape":
255
+ raise Cancelled
256
+ else:
257
+ continue
258
+ draw()
259
+ except (Cancelled, KeyboardInterrupt):
260
+ collapse()
261
+ answered(question, "cancelled", mark=CROSS)
262
+ raise Cancelled
263
+ collapse()
264
+ answered(question, ", ".join(label for label, value, _ in options if value in ticked))
265
+ return [value for _, value, _ in options if value in ticked]
266
+
267
+
192
268
  def confirm(question, default=True):
193
269
  """Yes or no; Enter takes the default."""
194
270
  return select(question, [("Yes", True), ("No", False)], default=0 if default else 1,
@@ -265,6 +341,109 @@ def step(label, command, cwd=None, env=None):
265
341
  return False, output
266
342
 
267
343
 
344
+ class Task:
345
+ """One line of work for tasks(): commands run one after another in cwd. A command is a list
346
+ (run directly) or a callable returning None on success or an error message."""
347
+
348
+ def __init__(self, label, commands, cwd=None, env=None):
349
+ self.label = label
350
+ self.commands = commands
351
+ self.cwd = cwd
352
+ self.env = env
353
+ self.state = "waiting" # waiting / running / done / failed
354
+ self.latest = ""
355
+ self.lines = []
356
+ self.failed_command = None # the command that failed, as text
357
+ self.started = None
358
+ self.elapsed = 0.0
359
+
360
+ def run(self):
361
+ self.state = "running"
362
+ self.started = time.monotonic()
363
+ for command in self.commands:
364
+ if callable(command):
365
+ # A step decided when it runs: returns None (done), an error message, or the
366
+ # command to run now (e.g. one naming an interpreter an earlier step created).
367
+ try:
368
+ result = command()
369
+ except Exception as error: # noqa: BLE001
370
+ result = str(error)
371
+ if isinstance(result, str):
372
+ self.lines.append(result)
373
+ self.failed_command = getattr(command, "description", "")
374
+ break
375
+ if result is None:
376
+ continue
377
+ command = result
378
+ command = list(command)
379
+ command[0] = shutil.which(command[0]) or command[0]
380
+ try:
381
+ process = subprocess.Popen(
382
+ command, cwd=self.cwd, env=self.env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
383
+ stdin=subprocess.DEVNULL, text=True, encoding="utf-8", errors="replace", bufsize=1,
384
+ )
385
+ except OSError as error:
386
+ self.lines.append(f"couldn't run {os.path.basename(command[0])} ({error.strerror or error})")
387
+ self.failed_command = " ".join([os.path.basename(command[0])] + command[1:])
388
+ break
389
+ for line in process.stdout:
390
+ line = line.strip()
391
+ if line:
392
+ self.lines.append(line)
393
+ self.latest = line
394
+ if process.wait() != 0:
395
+ self.failed_command = " ".join([os.path.splitext(os.path.basename(command[0]))[0]] + command[1:])
396
+ break
397
+ self.elapsed = time.monotonic() - self.started
398
+ self.state = "failed" if self.failed_command is not None else "done"
399
+
400
+
401
+ def tasks(items):
402
+ """Run Tasks side by side, one live line each (spinner, label, latest output, time). The
403
+ full output of a failed task is printed once everything has finished. Returns the tasks."""
404
+ threads = [threading.Thread(target=task.run, daemon=True) for task in items]
405
+ for thread in threads:
406
+ thread.start()
407
+ show = interactive()
408
+ frames = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
409
+ label_width = max(len(task.label) for task in items)
410
+
411
+ def line(task, frame):
412
+ width = _width()
413
+ label = task.label.ljust(label_width)
414
+ if task.state == "done":
415
+ return f"{CHECK} {label} {DIM}({task.elapsed:.0f}s){RESET}"
416
+ if task.state == "failed":
417
+ return f"{CROSS} {label} {Fore.RED}failed{RESET} {DIM}({task.elapsed:.0f}s){RESET}"
418
+ if task.state == "waiting":
419
+ return f"{DIM}·{RESET} {label}"
420
+ elapsed = f"{int(time.monotonic() - task.started)}s"
421
+ room = width - label_width - len(elapsed) - 6
422
+ latest = _truncate(task.latest, room) if task.latest and room > 10 else ""
423
+ return f"{Fore.CYAN}{frame}{RESET} {label} {DIM}{elapsed} {latest}{RESET}"
424
+
425
+ i = 0
426
+ if show:
427
+ _write("\n" * len(items))
428
+ while any(thread.is_alive() for thread in threads):
429
+ if show:
430
+ _write(_UP.format(len(items)))
431
+ for task in items:
432
+ _write(f"{_CLEAR_LINE}{line(task, frames[i % len(frames)])}\n")
433
+ i += 1
434
+ time.sleep(0.08)
435
+ if show:
436
+ _write(_UP.format(len(items)))
437
+ for task in items:
438
+ _write(f"{_CLEAR_LINE}{line(task, '')}\n")
439
+ for task in items:
440
+ if task.state == "failed":
441
+ _write(f"\n{CROSS} {Style.BRIGHT}{task.label}{RESET}: {task.failed_command or 'failed'}\n")
442
+ for output in task.lines[-15:]:
443
+ _write(f" {DIM}{output}{RESET}\n")
444
+ return items
445
+
446
+
268
447
  def _truncate(value, limit):
269
448
  return value if len(value) <= limit else value[: max(limit - 1, 1)] + "…"
270
449
 
@@ -5,7 +5,6 @@ a one-line answer, the defaults are already selected (Enter, Enter, Enter works)
5
5
  pauses for effect. The only spinner is for real work - installing dependencies - and it shows
6
6
  what that work is doing. Every answer can also be given as a flag; with --yes nothing is asked.
7
7
  """
8
- import json
9
8
  import os
10
9
  import shutil
11
10
  import sys
@@ -190,16 +189,9 @@ FRONTENDS = {
190
189
 
191
190
 
192
191
  def _create_frontend(plan):
193
- import importlib
194
- module_name, tool = FRONTENDS[plan.technology]
195
- module = importlib.import_module(module_name)
196
- with open(os.path.join(os.path.dirname(module.__file__), "structure.json"), encoding="utf-8") as f:
197
- structure = json.load(f)
198
- variables = {"project_name": plan.name}
199
- if plan.technology == "flutter":
200
- variables["project_camel"] = module.convert_to_camel_case(plan.name)
201
- os.makedirs(plan.root, exist_ok=True)
202
- module.create_from_json(plan.root, structure["{project_name}"], variables)
192
+ from ntc.workspace.create import render_frontend
193
+ tool = FRONTENDS[plan.technology][1]
194
+ render_frontend(plan.technology, plan.root, plan.name)
203
195
  prompts.success(f"Created {_count_files(plan.root)} files")
204
196
  if plan.install:
205
197
  command = ["npm", "install", "--no-audit", "--no-fund"] if tool == "npm" else ["flutter", "pub", "get"]
@@ -251,10 +243,18 @@ def _next_steps(plan):
251
243
 
252
244
  # ── The wizard ──────────────────────────────────────────────────────────
253
245
 
254
- def run(technology=None, name=None, path=None, with_auth=None, env_choice=None, install=None, assume_yes=False):
246
+ def run(technology=None, name=None, path=None, with_auth=None, env_choice=None, install=None, assume_yes=False,
247
+ apps=None, git=None):
255
248
  """Ask what isn't given, show the plan, create the project. Returns the project folder, or
256
- None if cancelled. Esc or Ctrl+C at any question cancels without writing anything."""
249
+ None if cancelled. Esc or Ctrl+C at any question cancels without writing anything.
250
+
251
+ Picking several apps (or passing apps=[roles]) makes a workspace instead: the apps side by
252
+ side in one folder, wired to each other, with an ntc.toml for `ntc start`."""
253
+ from ntc.workspace import config as workspace_config
257
254
  print(f"\n{Style.BRIGHT}ntc{RESET} {DIM}— new project{RESET}\n")
255
+ inside = workspace_config.find_root()
256
+ if inside and not apps and technology is None:
257
+ prompts.info(f"You're in the {os.path.basename(inside)} workspace - `ntc add` adds an app to it.")
258
258
  plan = Plan()
259
259
  try:
260
260
  if name is None:
@@ -267,10 +267,20 @@ def run(technology=None, name=None, path=None, with_auth=None, env_choice=None,
267
267
  prompts.answered("Project name", name)
268
268
  plan.name = name
269
269
 
270
- if technology is None:
271
- technology = prompts.select("What are you building?", TECHNOLOGIES)
270
+ if technology is None and not apps:
271
+ options = [(label, role, workspace_config.KINDS[kind]["label"])
272
+ for role, label, kind in workspace_config.ROLES]
273
+ apps = prompts.multiselect("What are you building?", options,
274
+ hint="Space to tick (several = a workspace), Enter to confirm")
275
+ if len(apps) == 1:
276
+ technology = workspace_config.ROLE_KINDS[apps[0]]
277
+ apps = None
278
+ elif apps:
279
+ prompts.answered("What are you building?", ", ".join(apps))
272
280
  else:
273
281
  prompts.answered("What are you building?", LABELS.get(technology, technology))
282
+ if apps:
283
+ raise _Workspace # a workspace asks its own questions from here
274
284
  plan.technology = technology
275
285
 
276
286
  _ask_location(plan, path, assume_yes)
@@ -294,6 +304,10 @@ def run(technology=None, name=None, path=None, with_auth=None, env_choice=None,
294
304
  except prompts.Cancelled:
295
305
  print(f"\n{DIM}Cancelled - nothing was written.{RESET}")
296
306
  return None
307
+ except _Workspace:
308
+ from ntc.workspace.create import run_init
309
+ return run_init(plan.name, apps, path=path, with_auth=with_auth, env_choice=env_choice,
310
+ install=install, git=git, assume_yes=assume_yes)
297
311
 
298
312
  print()
299
313
  started = time.monotonic()
@@ -312,6 +326,10 @@ def run(technology=None, name=None, path=None, with_auth=None, env_choice=None,
312
326
  return plan.root
313
327
 
314
328
 
329
+ class _Workspace(Exception):
330
+ """Several apps were picked: the workspace flow takes over."""
331
+
332
+
315
333
  def _is_valid_location_name(plan):
316
334
  """A project created in an existing folder takes that folder's name; make sure it's usable."""
317
335
  if plan.technology == "django" and not is_valid_project_name(plan.name):
@@ -0,0 +1,2 @@
1
+ """Workspaces: several apps (backend, website, dashboards, mobile) in one folder, described by
2
+ ntc.toml at its root, created by `ntc init` and run with `ntc start`."""