agentic-dev 0.2.9 → 0.2.11
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 +23 -8
- package/bin/agentic-dev.mjs +692 -55
- package/lib/scaffold.mjs +109 -6
- package/package.json +1 -1
- package/client/admin/.dockerignore +0 -3
- package/client/admin/.env.example +0 -1
- package/client/admin/Dockerfile +0 -16
- package/client/admin/Dockerfile.dev +0 -18
- package/client/admin/README.md +0 -20
- package/client/admin/index.html +0 -12
- package/client/admin/package.json +0 -41
- package/client/admin/postcss.config.js +0 -6
- package/client/admin/scripts/ui-parity-admin-adapter.mjs +0 -65
- package/client/admin/src/api/alerts.ts +0 -33
- package/client/admin/src/api/client.ts +0 -71
- package/client/admin/src/api/orders.ts +0 -33
- package/client/admin/src/api/support.ts +0 -11
- package/client/admin/src/app/App.tsx +0 -23
- package/client/admin/src/auth/AuthProvider.tsx +0 -122
- package/client/admin/src/auth/ProtectedRoute.tsx +0 -22
- package/client/admin/src/auth/auth-client.ts +0 -38
- package/client/admin/src/auth/types.ts +0 -18
- package/client/admin/src/components/AdminNotificationsDrawer.tsx +0 -162
- package/client/admin/src/components/AdminShell.tsx +0 -76
- package/client/admin/src/components/ui/button.tsx +0 -34
- package/client/admin/src/components/ui/input.tsx +0 -21
- package/client/admin/src/lib/cn.ts +0 -6
- package/client/admin/src/lib/specRouteCatalog.json +0 -30
- package/client/admin/src/lib/specScreens.json +0 -22
- package/client/admin/src/main.tsx +0 -17
- package/client/admin/src/pages/AdminDashboardPage.tsx +0 -171
- package/client/admin/src/pages/AdminLoginPage.tsx +0 -75
- package/client/admin/src/pages/AdminQueuePage.tsx +0 -107
- package/client/admin/src/pages/AdminSupportPage.tsx +0 -61
- package/client/admin/src/styles/globals.css +0 -17
- package/client/admin/src/theme-vars.ts +0 -18
- package/client/admin/src/theme.ts +0 -25
- package/client/admin/src/vite-env.d.ts +0 -1
- package/client/admin/tailwind.config.js +0 -8
- package/client/admin/tsconfig.json +0 -25
- package/client/admin/vite.config.ts +0 -12
- package/client/landing/.dockerignore +0 -3
- package/client/landing/.env.example +0 -1
- package/client/landing/Dockerfile +0 -16
- package/client/landing/Dockerfile.dev +0 -18
- package/client/landing/README.md +0 -18
- package/client/landing/index.html +0 -12
- package/client/landing/package.json +0 -41
- package/client/landing/postcss.config.js +0 -6
- package/client/landing/scripts/ui-parity-landing-adapter.mjs +0 -65
- package/client/landing/src/App.tsx +0 -21
- package/client/landing/src/api/catalog.ts +0 -30
- package/client/landing/src/api/client.ts +0 -30
- package/client/landing/src/auth/AuthProvider.tsx +0 -122
- package/client/landing/src/auth/ProtectedRoute.tsx +0 -22
- package/client/landing/src/auth/auth-client.ts +0 -38
- package/client/landing/src/auth/types.ts +0 -18
- package/client/landing/src/components/LandingShell.tsx +0 -34
- package/client/landing/src/lib/specRouteCatalog.json +0 -23
- package/client/landing/src/lib/specScreens.json +0 -17
- package/client/landing/src/main.tsx +0 -17
- package/client/landing/src/pages/LandingHomePage.tsx +0 -215
- package/client/landing/src/pages/LandingLoginPage.tsx +0 -90
- package/client/landing/src/pages/LandingWorkspacePage.tsx +0 -126
- package/client/landing/src/styles/globals.css +0 -17
- package/client/landing/src/theme-vars.ts +0 -16
- package/client/landing/src/theme.ts +0 -21
- package/client/landing/src/vite-env.d.ts +0 -1
- package/client/landing/tailwind.config.js +0 -8
- package/client/landing/tsconfig.json +0 -25
- package/client/landing/vite.config.ts +0 -12
- package/client/mobile/.dockerignore +0 -2
- package/client/mobile/.env.example +0 -1
- package/client/mobile/Dockerfile +0 -16
- package/client/mobile/Dockerfile.dev +0 -18
- package/client/mobile/README.md +0 -19
- package/client/mobile/index.html +0 -12
- package/client/mobile/package.json +0 -42
- package/client/mobile/postcss.config.js +0 -6
- package/client/mobile/scripts/ui-parity-mobile-adapter.mjs +0 -67
- package/client/mobile/src/App.tsx +0 -1
- package/client/mobile/src/api/client.ts +0 -62
- package/client/mobile/src/api/fulfillment.ts +0 -55
- package/client/mobile/src/api/shipping.ts +0 -56
- package/client/mobile/src/app/App.tsx +0 -23
- package/client/mobile/src/auth/AuthProvider.tsx +0 -122
- package/client/mobile/src/auth/ProtectedRoute.tsx +0 -27
- package/client/mobile/src/auth/auth-client.ts +0 -38
- package/client/mobile/src/auth/types.ts +0 -18
- package/client/mobile/src/components/InShell.tsx +0 -74
- package/client/mobile/src/components/ui/button.tsx +0 -35
- package/client/mobile/src/components/ui/card.tsx +0 -15
- package/client/mobile/src/components/ui/input.tsx +0 -21
- package/client/mobile/src/lib/cn.ts +0 -6
- package/client/mobile/src/lib/specRouteCatalog.json +0 -26
- package/client/mobile/src/lib/specScreens.json +0 -22
- package/client/mobile/src/lib/useSpeechRecognitionInput.ts +0 -271
- package/client/mobile/src/main.tsx +0 -17
- package/client/mobile/src/pages/DashboardPage.tsx +0 -172
- package/client/mobile/src/pages/FulfillmentPage.tsx +0 -138
- package/client/mobile/src/pages/LoginPage.tsx +0 -74
- package/client/mobile/src/pages/ShippingPage.tsx +0 -338
- package/client/mobile/src/styles/globals.css +0 -23
- package/client/mobile/src/theme-vars.ts +0 -16
- package/client/mobile/src/theme.ts +0 -21
- package/client/mobile/src/vite-env.d.ts +0 -1
- package/client/mobile/tailwind.config.js +0 -8
- package/client/mobile/tsconfig.json +0 -25
- package/client/mobile/vite.config.ts +0 -12
- package/client/web/.dockerignore +0 -3
- package/client/web/.env.example +0 -1
- package/client/web/Dockerfile +0 -16
- package/client/web/Dockerfile.dev +0 -18
- package/client/web/README.md +0 -47
- package/client/web/index.html +0 -12
- package/client/web/package.json +0 -42
- package/client/web/postcss.config.js +0 -6
- package/client/web/scripts/ui-parity-web-adapter.mjs +0 -66
- package/client/web/src/api/client.ts +0 -30
- package/client/web/src/api/orders.ts +0 -42
- package/client/web/src/app/App.tsx +0 -21
- package/client/web/src/auth/AuthProvider.tsx +0 -122
- package/client/web/src/auth/ProtectedRoute.tsx +0 -22
- package/client/web/src/auth/auth-client.ts +0 -38
- package/client/web/src/auth/types.ts +0 -18
- package/client/web/src/components/AppShell.tsx +0 -59
- package/client/web/src/components/ui/button.tsx +0 -35
- package/client/web/src/components/ui/card.tsx +0 -7
- package/client/web/src/components/ui/input.tsx +0 -21
- package/client/web/src/lib/cn.ts +0 -6
- package/client/web/src/lib/specRouteCatalog.json +0 -23
- package/client/web/src/lib/specScreens.json +0 -17
- package/client/web/src/main.tsx +0 -17
- package/client/web/src/pages/DashboardPage.tsx +0 -158
- package/client/web/src/pages/LoginPage.tsx +0 -72
- package/client/web/src/pages/OrdersPage.tsx +0 -123
- package/client/web/src/styles/globals.css +0 -17
- package/client/web/src/theme-vars.ts +0 -18
- package/client/web/src/theme.ts +0 -25
- package/client/web/src/vite-env.d.ts +0 -1
- package/client/web/tailwind.config.js +0 -8
- package/client/web/tsconfig.json +0 -25
- package/client/web/vite.config.ts +0 -12
- package/server/.dockerignore +0 -4
- package/server/.env.example +0 -19
- package/server/Dockerfile +0 -22
- package/server/Dockerfile.dev +0 -19
- package/server/README.md +0 -33
- package/server/__init__.py +0 -0
- package/server/api/__init__.py +0 -1
- package/server/api/http/__init__.py +0 -4
- package/server/api/http/app.py +0 -53
- package/server/api/http/router.py +0 -24
- package/server/config.py +0 -52
- package/server/contexts/__init__.py +0 -12
- package/server/contexts/alerts/__init__.py +0 -1
- package/server/contexts/alerts/application/__init__.py +0 -13
- package/server/contexts/alerts/application/services.py +0 -41
- package/server/contexts/alerts/contracts/__init__.py +0 -3
- package/server/contexts/alerts/contracts/http/__init__.py +0 -3
- package/server/contexts/alerts/contracts/http/router.py +0 -37
- package/server/contexts/alerts/domain/__init__.py +0 -15
- package/server/contexts/alerts/domain/models.py +0 -29
- package/server/contexts/alerts/infrastructure/__init__.py +0 -11
- package/server/contexts/alerts/infrastructure/repository.py +0 -41
- package/server/contexts/auth/__init__.py +0 -1
- package/server/contexts/auth/application/__init__.py +0 -3
- package/server/contexts/auth/application/ports.py +0 -10
- package/server/contexts/auth/application/services.py +0 -64
- package/server/contexts/auth/contracts/__init__.py +0 -4
- package/server/contexts/auth/contracts/http/__init__.py +0 -4
- package/server/contexts/auth/contracts/http/dependencies.py +0 -37
- package/server/contexts/auth/contracts/http/router.py +0 -19
- package/server/contexts/auth/domain/__init__.py +0 -3
- package/server/contexts/auth/domain/models.py +0 -24
- package/server/contexts/auth/infrastructure/__init__.py +0 -4
- package/server/contexts/auth/infrastructure/adapters/memory.py +0 -19
- package/server/contexts/auth/infrastructure/adapters/mongodb.py +0 -24
- package/server/contexts/auth/infrastructure/adapters/sqlalchemy.py +0 -74
- package/server/contexts/auth/infrastructure/repository.py +0 -28
- package/server/contexts/catalog/__init__.py +0 -1
- package/server/contexts/catalog/application/__init__.py +0 -28
- package/server/contexts/catalog/application/ports.py +0 -15
- package/server/contexts/catalog/application/services.py +0 -154
- package/server/contexts/catalog/contracts/__init__.py +0 -3
- package/server/contexts/catalog/contracts/http/__init__.py +0 -3
- package/server/contexts/catalog/contracts/http/router.py +0 -60
- package/server/contexts/catalog/domain/__init__.py +0 -45
- package/server/contexts/catalog/domain/models.py +0 -113
- package/server/contexts/catalog/infrastructure/__init__.py +0 -4
- package/server/contexts/catalog/infrastructure/adapters/memory.py +0 -62
- package/server/contexts/catalog/infrastructure/repository.py +0 -8
- package/server/contexts/fulfillment/__init__.py +0 -1
- package/server/contexts/fulfillment/application/__init__.py +0 -13
- package/server/contexts/fulfillment/application/ports.py +0 -20
- package/server/contexts/fulfillment/application/services.py +0 -85
- package/server/contexts/fulfillment/contracts/__init__.py +0 -3
- package/server/contexts/fulfillment/contracts/http/__init__.py +0 -3
- package/server/contexts/fulfillment/contracts/http/router.py +0 -40
- package/server/contexts/fulfillment/domain/__init__.py +0 -25
- package/server/contexts/fulfillment/domain/models.py +0 -73
- package/server/contexts/fulfillment/infrastructure/__init__.py +0 -13
- package/server/contexts/fulfillment/infrastructure/adapters/memory.py +0 -43
- package/server/contexts/fulfillment/infrastructure/repository.py +0 -97
- package/server/contexts/health/__init__.py +0 -1
- package/server/contexts/health/application/__init__.py +0 -3
- package/server/contexts/health/application/services.py +0 -2
- package/server/contexts/health/contracts/__init__.py +0 -3
- package/server/contexts/health/contracts/http/__init__.py +0 -3
- package/server/contexts/health/contracts/http/router.py +0 -10
- package/server/contexts/inventory/__init__.py +0 -1
- package/server/contexts/inventory/application/__init__.py +0 -28
- package/server/contexts/inventory/application/ports.py +0 -11
- package/server/contexts/inventory/application/services.py +0 -214
- package/server/contexts/inventory/contracts/__init__.py +0 -3
- package/server/contexts/inventory/contracts/http/__init__.py +0 -3
- package/server/contexts/inventory/contracts/http/router.py +0 -82
- package/server/contexts/inventory/domain/__init__.py +0 -33
- package/server/contexts/inventory/domain/models.py +0 -93
- package/server/contexts/inventory/infrastructure/__init__.py +0 -4
- package/server/contexts/inventory/infrastructure/adapters/memory.py +0 -24
- package/server/contexts/inventory/infrastructure/repository.py +0 -8
- package/server/contexts/orders/__init__.py +0 -1
- package/server/contexts/orders/application/__init__.py +0 -19
- package/server/contexts/orders/application/services.py +0 -127
- package/server/contexts/orders/contracts/__init__.py +0 -3
- package/server/contexts/orders/contracts/http/__init__.py +0 -3
- package/server/contexts/orders/contracts/http/router.py +0 -82
- package/server/contexts/orders/domain/__init__.py +0 -29
- package/server/contexts/orders/domain/models.py +0 -95
- package/server/contexts/orders/infrastructure/__init__.py +0 -7
- package/server/contexts/orders/infrastructure/repository.py +0 -104
- package/server/contexts/shipping/__init__.py +0 -1
- package/server/contexts/shipping/application/__init__.py +0 -13
- package/server/contexts/shipping/application/services.py +0 -92
- package/server/contexts/shipping/contracts/__init__.py +0 -3
- package/server/contexts/shipping/contracts/http/__init__.py +0 -3
- package/server/contexts/shipping/contracts/http/router.py +0 -40
- package/server/contexts/shipping/domain/__init__.py +0 -19
- package/server/contexts/shipping/domain/models.py +0 -48
- package/server/contexts/shipping/infrastructure/__init__.py +0 -9
- package/server/contexts/shipping/infrastructure/repository.py +0 -50
- package/server/contexts/support/__init__.py +0 -1
- package/server/contexts/support/application/__init__.py +0 -13
- package/server/contexts/support/application/services.py +0 -29
- package/server/contexts/support/contracts/__init__.py +0 -3
- package/server/contexts/support/contracts/http/__init__.py +0 -3
- package/server/contexts/support/contracts/http/router.py +0 -40
- package/server/contexts/support/domain/__init__.py +0 -13
- package/server/contexts/support/domain/models.py +0 -27
- package/server/contexts/support/infrastructure/__init__.py +0 -11
- package/server/contexts/support/infrastructure/repository.py +0 -70
- package/server/contexts/user/__init__.py +0 -1
- package/server/contexts/user/application/__init__.py +0 -3
- package/server/contexts/user/application/ports.py +0 -11
- package/server/contexts/user/application/services.py +0 -44
- package/server/contexts/user/contracts/__init__.py +0 -3
- package/server/contexts/user/contracts/http/__init__.py +0 -3
- package/server/contexts/user/contracts/http/router.py +0 -26
- package/server/contexts/user/domain/__init__.py +0 -3
- package/server/contexts/user/domain/models.py +0 -22
- package/server/contexts/user/infrastructure/__init__.py +0 -3
- package/server/contexts/user/infrastructure/adapters/memory.py +0 -27
- package/server/contexts/user/infrastructure/adapters/mongodb.py +0 -41
- package/server/contexts/user/infrastructure/adapters/sqlalchemy.py +0 -94
- package/server/contexts/user/infrastructure/factory.py +0 -28
- package/server/data/README.md +0 -24
- package/server/data/bootstrap/alerts.json +0 -38
- package/server/data/bootstrap/auth_accounts.json +0 -18
- package/server/data/bootstrap/catalog_products.json +0 -179
- package/server/data/bootstrap/fulfillment_events.json +0 -5
- package/server/data/bootstrap/fulfillment_notes.json +0 -5
- package/server/data/bootstrap/fulfillment_tasks.json +0 -50
- package/server/data/bootstrap/inventory_levels.json +0 -80
- package/server/data/bootstrap/orders.json +0 -62
- package/server/data/bootstrap/shipping_shipments.json +0 -50
- package/server/data/bootstrap/support_faqs.json +0 -26
- package/server/data/bootstrap/users.json +0 -20
- package/server/data/bootstrap_loader.py +0 -15
- package/server/docker-entrypoint.sh +0 -56
- package/server/main.py +0 -3
- package/server/pyproject.toml +0 -36
- package/server/shared/__init__.py +0 -1
- package/server/shared/application/__init__.py +0 -3
- package/server/shared/application/health.py +0 -2
- package/server/shared/infrastructure/__init__.py +0 -10
- package/server/shared/infrastructure/runtime.py +0 -6
- package/server/shared/infrastructure/security.py +0 -33
- package/server/tests/e2e/test_domain_feature_flows.py +0 -483
- package/server/tests/test_health.py +0 -49
- package/server/uv.lock +0 -1169
package/bin/agentic-dev.mjs
CHANGED
|
@@ -1,18 +1,90 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import process from "node:process";
|
|
4
5
|
import * as readline from "node:readline";
|
|
5
6
|
import { createInterface } from "node:readline/promises";
|
|
6
7
|
import {
|
|
8
|
+
DEFAULT_TEMPLATE_OWNER,
|
|
7
9
|
ensureTargetDir,
|
|
8
10
|
fetchTemplateRepos,
|
|
9
11
|
installTemplateRepo,
|
|
10
12
|
parseArgs,
|
|
11
13
|
resolveTemplateRepo,
|
|
12
|
-
selectTemplateRepo,
|
|
13
14
|
usage,
|
|
14
15
|
} from "../lib/scaffold.mjs";
|
|
15
16
|
|
|
17
|
+
const DEFAULT_TARGET_DIR = ".";
|
|
18
|
+
const DEFAULT_APP_MODE = "fullstack";
|
|
19
|
+
const DEFAULT_AI_PROVIDERS = ["codex", "claude"];
|
|
20
|
+
|
|
21
|
+
const GITHUB_AUTH_CHOICES = [
|
|
22
|
+
{
|
|
23
|
+
label: "public",
|
|
24
|
+
value: "public",
|
|
25
|
+
description: "Use public template-* repos from say828 without a token",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
label: "env",
|
|
29
|
+
value: "env",
|
|
30
|
+
description: "Use GH_TOKEN, GITHUB_TOKEN, or AGENTIC_GITHUB_TOKEN from the shell",
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
label: "pat",
|
|
34
|
+
value: "pat",
|
|
35
|
+
description: "Paste a GitHub PAT for this run only",
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
const APP_MODE_CHOICES = [
|
|
40
|
+
{
|
|
41
|
+
label: "fullstack",
|
|
42
|
+
value: "fullstack",
|
|
43
|
+
description: "Install dependencies and run frontend parity bootstrap",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
label: "frontend",
|
|
47
|
+
value: "frontend",
|
|
48
|
+
description: "Keep frontend-focused setup with browser install and parity bootstrap",
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
label: "backend",
|
|
52
|
+
value: "backend",
|
|
53
|
+
description: "Install workspace dependencies but skip browser install and parity bootstrap",
|
|
54
|
+
},
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
const AI_PROVIDER_CHOICES = [
|
|
58
|
+
{
|
|
59
|
+
label: "codex",
|
|
60
|
+
value: "codex",
|
|
61
|
+
description: "Keep Codex workspace config in the generated repo",
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
label: "claude",
|
|
65
|
+
value: "claude",
|
|
66
|
+
description: "Keep Claude workspace config in the generated repo",
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
label: "ollama",
|
|
70
|
+
value: "ollama",
|
|
71
|
+
description: "Record Ollama as a provider in setup metadata",
|
|
72
|
+
},
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
const CONFIRM_CHOICES = [
|
|
76
|
+
{
|
|
77
|
+
label: "Proceed",
|
|
78
|
+
value: "proceed",
|
|
79
|
+
description: "Run clone, install, and bootstrap now",
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
label: "Cancel",
|
|
83
|
+
value: "cancel",
|
|
84
|
+
description: "Stop without writing files",
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
|
|
16
88
|
function clearMenu(lines) {
|
|
17
89
|
if (lines <= 0) {
|
|
18
90
|
return;
|
|
@@ -21,18 +93,318 @@ function clearMenu(lines) {
|
|
|
21
93
|
readline.clearScreenDown(process.stdout);
|
|
22
94
|
}
|
|
23
95
|
|
|
24
|
-
function
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
96
|
+
function inferProjectName(targetDir) {
|
|
97
|
+
const normalized = (targetDir || DEFAULT_TARGET_DIR).trim() || DEFAULT_TARGET_DIR;
|
|
98
|
+
const resolved = path.resolve(process.cwd(), normalized);
|
|
99
|
+
return path.basename(resolved);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function directoryHasUserFiles(targetDir) {
|
|
103
|
+
const normalized = (targetDir || DEFAULT_TARGET_DIR).trim() || DEFAULT_TARGET_DIR;
|
|
104
|
+
const resolved = path.resolve(process.cwd(), normalized);
|
|
105
|
+
if (!fs.existsSync(resolved)) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const entries = fs
|
|
110
|
+
.readdirSync(resolved, { withFileTypes: true })
|
|
111
|
+
.filter((entry) => entry.name !== ".git");
|
|
112
|
+
return entries.length > 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function normalizeProviders(providers) {
|
|
116
|
+
const normalized = Array.isArray(providers)
|
|
117
|
+
? providers.map((provider) => provider.trim()).filter(Boolean)
|
|
118
|
+
: [];
|
|
119
|
+
if (normalized.length > 0) {
|
|
120
|
+
return [...new Set(normalized)];
|
|
121
|
+
}
|
|
122
|
+
return [...DEFAULT_AI_PROVIDERS];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function uniqueProviders(providers) {
|
|
126
|
+
return Array.isArray(providers)
|
|
127
|
+
? [...new Set(providers.map((provider) => provider.trim()).filter(Boolean))]
|
|
128
|
+
: [];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function hydrateOptions(options) {
|
|
132
|
+
const state = {
|
|
133
|
+
targetDir: (options.targetDir || DEFAULT_TARGET_DIR).trim() || DEFAULT_TARGET_DIR,
|
|
134
|
+
projectName: (options.projectName || "").trim(),
|
|
135
|
+
template: (options.template || "").trim(),
|
|
136
|
+
githubAuthMode: (options.githubAuthMode || "public").trim() || "public",
|
|
137
|
+
githubPat: options.githubPat || "",
|
|
138
|
+
appMode: (options.appMode || DEFAULT_APP_MODE).trim() || DEFAULT_APP_MODE,
|
|
139
|
+
aiProviders: normalizeProviders(options.aiProviders),
|
|
140
|
+
force: Boolean(options.force),
|
|
141
|
+
skipBootstrap: Boolean(options.skipBootstrap),
|
|
142
|
+
owner: options.owner || DEFAULT_TEMPLATE_OWNER,
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
if (!state.projectName) {
|
|
146
|
+
state.projectName = inferProjectName(state.targetDir);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return state;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function applyRuntimeGitHubAuth(state) {
|
|
153
|
+
if (state.githubAuthMode === "pat" && state.githubPat) {
|
|
154
|
+
process.env.AGENTIC_GITHUB_TOKEN = state.githubPat;
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (state.githubAuthMode === "public") {
|
|
159
|
+
delete process.env.AGENTIC_GITHUB_TOKEN;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function sanitizeStateForInstall(state) {
|
|
164
|
+
return {
|
|
165
|
+
...state,
|
|
166
|
+
targetDir: (state.targetDir || DEFAULT_TARGET_DIR).trim() || DEFAULT_TARGET_DIR,
|
|
167
|
+
projectName: (state.projectName || inferProjectName(state.targetDir)).trim(),
|
|
168
|
+
aiProviders: normalizeProviders(state.aiProviders),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function buildTemplateChoices(repos) {
|
|
173
|
+
return repos.map((repo) => ({
|
|
174
|
+
label: repo.name,
|
|
175
|
+
value: repo.name,
|
|
176
|
+
description: repo.description || "Public template repo",
|
|
177
|
+
}));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function buildSteps(state, repos) {
|
|
181
|
+
const steps = [
|
|
182
|
+
{
|
|
183
|
+
key: "targetDir",
|
|
184
|
+
type: "text",
|
|
185
|
+
label: "Project directory",
|
|
186
|
+
description: "Type the destination directory, then press Enter. Use ←/→ to move between screens.",
|
|
187
|
+
placeholder: DEFAULT_TARGET_DIR,
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
key: "projectName",
|
|
191
|
+
type: "text",
|
|
192
|
+
label: "Project name",
|
|
193
|
+
description: "Written into scaffold metadata and Claude workspace nickname.",
|
|
194
|
+
placeholder: inferProjectName(state.targetDir),
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
key: "githubAuthMode",
|
|
198
|
+
type: "single",
|
|
199
|
+
label: "GitHub auth mode",
|
|
200
|
+
description: "Choose how this run should access template repos.",
|
|
201
|
+
choices: GITHUB_AUTH_CHOICES,
|
|
202
|
+
},
|
|
203
|
+
];
|
|
204
|
+
|
|
205
|
+
if (state.githubAuthMode === "pat") {
|
|
206
|
+
steps.push({
|
|
207
|
+
key: "githubPat",
|
|
208
|
+
type: "password",
|
|
209
|
+
label: "GitHub PAT",
|
|
210
|
+
description: "Used only for this run. The token is not written into the generated repo.",
|
|
211
|
+
placeholder: "",
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
steps.push(
|
|
216
|
+
{
|
|
217
|
+
key: "template",
|
|
218
|
+
type: "single",
|
|
219
|
+
label: `Template repo from ${state.owner}`,
|
|
220
|
+
description: "Pick the public template-* repo to scaffold.",
|
|
221
|
+
choices: buildTemplateChoices(repos),
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
key: "appMode",
|
|
225
|
+
type: "single",
|
|
226
|
+
label: "App mode",
|
|
227
|
+
description: "Choose how much of the bootstrap flow should run after clone.",
|
|
228
|
+
choices: APP_MODE_CHOICES,
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
key: "aiProviders",
|
|
232
|
+
type: "multi",
|
|
233
|
+
label: "AI providers",
|
|
234
|
+
description: "Use Space to toggle. At least one provider must stay selected.",
|
|
235
|
+
choices: AI_PROVIDER_CHOICES,
|
|
236
|
+
},
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
if (!state.force && directoryHasUserFiles(state.targetDir)) {
|
|
240
|
+
steps.push({
|
|
241
|
+
key: "force",
|
|
242
|
+
type: "single",
|
|
243
|
+
label: "Target directory is not empty",
|
|
244
|
+
description: "Choose whether scaffolding may continue in the current directory.",
|
|
245
|
+
choices: [
|
|
246
|
+
{
|
|
247
|
+
label: "Continue",
|
|
248
|
+
value: true,
|
|
249
|
+
description: "Allow scaffolding into the existing directory",
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
label: "Cancel",
|
|
253
|
+
value: false,
|
|
254
|
+
description: "Stop without changing files",
|
|
255
|
+
},
|
|
256
|
+
],
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
steps.push({
|
|
261
|
+
key: "confirm",
|
|
262
|
+
type: "confirm",
|
|
263
|
+
label: "Review and run",
|
|
264
|
+
description: "Confirm every choice before the CLI starts cloning or installing.",
|
|
265
|
+
choices: CONFIRM_CHOICES,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
return steps;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function stepValue(state, step) {
|
|
272
|
+
if (step.key === "targetDir") {
|
|
273
|
+
return state.targetDir || step.placeholder || DEFAULT_TARGET_DIR;
|
|
274
|
+
}
|
|
275
|
+
if (step.key === "projectName") {
|
|
276
|
+
return state.projectName || step.placeholder || inferProjectName(state.targetDir);
|
|
277
|
+
}
|
|
278
|
+
if (step.key === "githubPat") {
|
|
279
|
+
return state.githubPat || "";
|
|
30
280
|
}
|
|
281
|
+
return state[step.key];
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function renderHeader(step, index, total) {
|
|
285
|
+
return [
|
|
286
|
+
`Agentic Dev Setup ${index + 1}/${total}`,
|
|
287
|
+
step.label,
|
|
288
|
+
step.description,
|
|
289
|
+
"",
|
|
290
|
+
];
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function renderTextStep(step, state, buffers, index, total) {
|
|
294
|
+
const lines = renderHeader(step, index, total);
|
|
295
|
+
if (!buffers.has(step.key)) {
|
|
296
|
+
buffers.set(step.key, stepValue(state, step) || "");
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const rawValue = buffers.get(step.key);
|
|
300
|
+
const displayValue =
|
|
301
|
+
step.type === "password" ? "*".repeat(rawValue.length) : rawValue || step.placeholder || "";
|
|
302
|
+
|
|
303
|
+
lines.push(`Value: ${displayValue}`);
|
|
304
|
+
lines.push("");
|
|
305
|
+
lines.push("Controls: type text, Backspace to edit, Enter or → to continue, ← to go back.");
|
|
306
|
+
process.stdout.write(`${lines.join("\n")}\n`);
|
|
307
|
+
return lines.length;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function renderSingleChoiceStep(step, state, cursors, index, total) {
|
|
311
|
+
const lines = renderHeader(step, index, total);
|
|
312
|
+
if (!cursors.has(step.key)) {
|
|
313
|
+
const currentValue = stepValue(state, step);
|
|
314
|
+
const currentIndex = Math.max(
|
|
315
|
+
0,
|
|
316
|
+
step.choices.findIndex((choice) => choice.value === currentValue),
|
|
317
|
+
);
|
|
318
|
+
cursors.set(step.key, currentIndex >= 0 ? currentIndex : 0);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const cursor = cursors.get(step.key);
|
|
322
|
+
step.choices.forEach((choice, choiceIndex) => {
|
|
323
|
+
const pointer = choiceIndex === cursor ? ">" : " ";
|
|
324
|
+
const suffix = choice.description ? ` - ${choice.description}` : "";
|
|
325
|
+
lines.push(`${pointer} ${choice.label}${suffix}`);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
lines.push("");
|
|
329
|
+
lines.push("Controls: ↑/↓ to choose, Enter or → to continue, ← to go back.");
|
|
330
|
+
process.stdout.write(`${lines.join("\n")}\n`);
|
|
331
|
+
return lines.length;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function renderMultiChoiceStep(step, state, cursors, index, total) {
|
|
335
|
+
const lines = renderHeader(step, index, total);
|
|
336
|
+
if (!cursors.has(step.key)) {
|
|
337
|
+
cursors.set(step.key, 0);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const selected = new Set(uniqueProviders(stepValue(state, step)));
|
|
341
|
+
const cursor = cursors.get(step.key);
|
|
342
|
+
step.choices.forEach((choice, choiceIndex) => {
|
|
343
|
+
const pointer = choiceIndex === cursor ? ">" : " ";
|
|
344
|
+
const checked = selected.has(choice.value) ? "[x]" : "[ ]";
|
|
345
|
+
const suffix = choice.description ? ` - ${choice.description}` : "";
|
|
346
|
+
lines.push(`${pointer} ${checked} ${choice.label}${suffix}`);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
lines.push("");
|
|
350
|
+
lines.push("Controls: ↑/↓ to move, Space to toggle, Enter or → to continue, ← to go back.");
|
|
351
|
+
process.stdout.write(`${lines.join("\n")}\n`);
|
|
352
|
+
return lines.length;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function executionPreview(state, selectedRepo) {
|
|
356
|
+
const lines = [
|
|
357
|
+
`Project directory: ${path.resolve(process.cwd(), state.targetDir)}`,
|
|
358
|
+
`Project name: ${state.projectName}`,
|
|
359
|
+
`GitHub auth mode: ${state.githubAuthMode}`,
|
|
360
|
+
`Template repo: ${selectedRepo?.name || state.template}`,
|
|
361
|
+
`App mode: ${state.appMode}`,
|
|
362
|
+
`AI providers: ${state.aiProviders.join(", ")}`,
|
|
363
|
+
`Allow non-empty directory: ${state.force ? "yes" : "no"}`,
|
|
364
|
+
];
|
|
365
|
+
|
|
366
|
+
if (state.githubAuthMode === "pat") {
|
|
367
|
+
lines.push(`GitHub PAT supplied: ${state.githubPat ? "yes" : "no"}`);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
lines.push("Run plan:");
|
|
371
|
+
lines.push(` 1. Clone ${selectedRepo?.name || state.template}`);
|
|
372
|
+
lines.push(" 2. Copy .env.example to .env if needed");
|
|
373
|
+
lines.push(" 3. Run pnpm install");
|
|
374
|
+
if (state.appMode === "backend") {
|
|
375
|
+
lines.push(" 4. Skip browser install and parity bootstrap");
|
|
376
|
+
} else {
|
|
377
|
+
lines.push(" 4. Install Playwright Chromium for the default frontend target");
|
|
378
|
+
lines.push(" 5. Run frontend parity bootstrap");
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return lines;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function renderConfirmStep(step, state, repos, cursors, index, total) {
|
|
385
|
+
const lines = renderHeader(step, index, total);
|
|
386
|
+
const selectedRepo = resolveTemplateRepo(state.template, repos);
|
|
387
|
+
executionPreview(state, selectedRepo).forEach((line) => lines.push(line));
|
|
388
|
+
lines.push("");
|
|
389
|
+
|
|
390
|
+
if (!cursors.has(step.key)) {
|
|
391
|
+
cursors.set(step.key, 0);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const cursor = cursors.get(step.key);
|
|
395
|
+
step.choices.forEach((choice, choiceIndex) => {
|
|
396
|
+
const pointer = choiceIndex === cursor ? ">" : " ";
|
|
397
|
+
const suffix = choice.description ? ` - ${choice.description}` : "";
|
|
398
|
+
lines.push(`${pointer} ${choice.label}${suffix}`);
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
lines.push("");
|
|
402
|
+
lines.push("Controls: ↑/↓ to choose, Enter or → to confirm, ← to go back.");
|
|
31
403
|
process.stdout.write(`${lines.join("\n")}\n`);
|
|
32
404
|
return lines.length;
|
|
33
405
|
}
|
|
34
406
|
|
|
35
|
-
async function
|
|
407
|
+
async function runInteractiveSession(render, onInput) {
|
|
36
408
|
const stdin = process.stdin;
|
|
37
409
|
const stdout = process.stdout;
|
|
38
410
|
const previousRawMode = typeof stdin.setRawMode === "function" ? stdin.isRaw : undefined;
|
|
@@ -78,80 +450,315 @@ async function runArrowMenu(render, onInput) {
|
|
|
78
450
|
});
|
|
79
451
|
}
|
|
80
452
|
|
|
81
|
-
|
|
82
|
-
if (
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
453
|
+
function validateStepValue(step, state, value) {
|
|
454
|
+
if (step.key === "targetDir") {
|
|
455
|
+
const trimmed = value.trim() || DEFAULT_TARGET_DIR;
|
|
456
|
+
return trimmed;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (step.key === "projectName") {
|
|
460
|
+
const trimmed = value.trim();
|
|
461
|
+
if (!trimmed) {
|
|
462
|
+
throw new Error("Project name cannot be empty.");
|
|
463
|
+
}
|
|
464
|
+
return trimmed;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if (step.key === "githubPat") {
|
|
468
|
+
const trimmed = value.trim();
|
|
469
|
+
if (!trimmed) {
|
|
470
|
+
throw new Error("GitHub PAT cannot be empty.");
|
|
471
|
+
}
|
|
472
|
+
return trimmed;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (step.key === "aiProviders") {
|
|
476
|
+
const providers = normalizeProviders(value);
|
|
477
|
+
if (providers.length === 0) {
|
|
478
|
+
throw new Error("Select at least one AI provider.");
|
|
479
|
+
}
|
|
480
|
+
return providers;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
if (step.key === "force" && value === false) {
|
|
484
|
+
throw new Error("Prompt cancelled");
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
return value;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function setStateValue(state, step, value) {
|
|
491
|
+
if (step.key === "targetDir") {
|
|
492
|
+
const previousInferred = inferProjectName(state.targetDir);
|
|
493
|
+
state.targetDir = value;
|
|
494
|
+
if (!state.projectName || state.projectName === previousInferred) {
|
|
495
|
+
state.projectName = inferProjectName(value);
|
|
496
|
+
}
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
state[step.key] = value;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
async function runWizard(initialState, repos) {
|
|
504
|
+
const state = sanitizeStateForInstall(initialState);
|
|
505
|
+
const buffers = new Map();
|
|
506
|
+
const cursors = new Map();
|
|
507
|
+
let stepIndex = 0;
|
|
508
|
+
|
|
509
|
+
return runInteractiveSession(
|
|
510
|
+
() => {
|
|
511
|
+
const steps = buildSteps(state, repos);
|
|
512
|
+
if (stepIndex >= steps.length) {
|
|
513
|
+
stepIndex = steps.length - 1;
|
|
514
|
+
}
|
|
515
|
+
const step = steps[stepIndex];
|
|
516
|
+
process.stdout.write("\n");
|
|
517
|
+
if (step.type === "text" || step.type === "password") {
|
|
518
|
+
return renderTextStep(step, state, buffers, stepIndex, steps.length);
|
|
519
|
+
}
|
|
520
|
+
if (step.type === "multi") {
|
|
521
|
+
return renderMultiChoiceStep(step, state, cursors, stepIndex, steps.length);
|
|
522
|
+
}
|
|
523
|
+
if (step.type === "confirm") {
|
|
524
|
+
return renderConfirmStep(step, state, repos, cursors, stepIndex, steps.length);
|
|
525
|
+
}
|
|
526
|
+
return renderSingleChoiceStep(step, state, cursors, stepIndex, steps.length);
|
|
527
|
+
},
|
|
528
|
+
(chunk, rerender) => {
|
|
529
|
+
const steps = buildSteps(state, repos);
|
|
530
|
+
const step = steps[stepIndex];
|
|
531
|
+
|
|
532
|
+
if (chunk === "\u0003") {
|
|
533
|
+
throw new Error("Prompt cancelled");
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
if (step.type === "text" || step.type === "password") {
|
|
537
|
+
if (!buffers.has(step.key)) {
|
|
538
|
+
buffers.set(step.key, stepValue(state, step) || "");
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (chunk === "\u007f") {
|
|
542
|
+
buffers.set(step.key, buffers.get(step.key).slice(0, -1));
|
|
543
|
+
rerender();
|
|
544
|
+
return undefined;
|
|
90
545
|
}
|
|
91
|
-
|
|
92
|
-
|
|
546
|
+
|
|
547
|
+
if (chunk === "\u001b[D") {
|
|
548
|
+
if (stepIndex > 0) {
|
|
549
|
+
stepIndex -= 1;
|
|
550
|
+
rerender();
|
|
551
|
+
}
|
|
552
|
+
return undefined;
|
|
93
553
|
}
|
|
94
|
-
|
|
95
|
-
|
|
554
|
+
|
|
555
|
+
if (chunk === "\r" || chunk === "\n" || chunk === "\u001b[C") {
|
|
556
|
+
const nextValue = validateStepValue(step, state, buffers.get(step.key));
|
|
557
|
+
setStateValue(state, step, nextValue);
|
|
558
|
+
const nextSteps = buildSteps(state, repos);
|
|
559
|
+
if (stepIndex < nextSteps.length - 1) {
|
|
560
|
+
stepIndex += 1;
|
|
561
|
+
rerender();
|
|
562
|
+
return undefined;
|
|
563
|
+
}
|
|
564
|
+
return sanitizeStateForInstall(state);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
if (/^[\x20-\x7e]$/.test(chunk)) {
|
|
568
|
+
buffers.set(step.key, `${buffers.get(step.key)}${chunk}`);
|
|
96
569
|
rerender();
|
|
97
|
-
}
|
|
98
|
-
|
|
570
|
+
}
|
|
571
|
+
return undefined;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if (!cursors.has(step.key)) {
|
|
575
|
+
const currentValue = stepValue(state, step);
|
|
576
|
+
const initialIndex = Math.max(
|
|
577
|
+
0,
|
|
578
|
+
step.choices.findIndex((choice) => choice.value === currentValue),
|
|
579
|
+
);
|
|
580
|
+
cursors.set(step.key, initialIndex >= 0 ? initialIndex : 0);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
if (chunk === "\u001b[D") {
|
|
584
|
+
if (stepIndex > 0) {
|
|
585
|
+
stepIndex -= 1;
|
|
99
586
|
rerender();
|
|
100
587
|
}
|
|
101
588
|
return undefined;
|
|
102
|
-
}
|
|
103
|
-
);
|
|
104
|
-
}
|
|
589
|
+
}
|
|
105
590
|
|
|
591
|
+
if (chunk === "\u001b[A") {
|
|
592
|
+
const nextCursor = (cursors.get(step.key) - 1 + step.choices.length) % step.choices.length;
|
|
593
|
+
cursors.set(step.key, nextCursor);
|
|
594
|
+
rerender();
|
|
595
|
+
return undefined;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
if (chunk === "\u001b[B") {
|
|
599
|
+
const nextCursor = (cursors.get(step.key) + 1) % step.choices.length;
|
|
600
|
+
cursors.set(step.key, nextCursor);
|
|
601
|
+
rerender();
|
|
602
|
+
return undefined;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
if (step.type === "multi" && chunk === " ") {
|
|
606
|
+
const selected = new Set(uniqueProviders(state.aiProviders));
|
|
607
|
+
const currentChoice = step.choices[cursors.get(step.key)];
|
|
608
|
+
if (selected.has(currentChoice.value)) {
|
|
609
|
+
selected.delete(currentChoice.value);
|
|
610
|
+
} else {
|
|
611
|
+
selected.add(currentChoice.value);
|
|
612
|
+
}
|
|
613
|
+
state.aiProviders = [...selected];
|
|
614
|
+
rerender();
|
|
615
|
+
return undefined;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
if (chunk === "\r" || chunk === "\n" || chunk === "\u001b[C") {
|
|
619
|
+
if (step.type === "multi") {
|
|
620
|
+
const providers = validateStepValue(step, state, state.aiProviders);
|
|
621
|
+
setStateValue(state, step, providers);
|
|
622
|
+
} else {
|
|
623
|
+
const selectedChoice = step.choices[cursors.get(step.key)];
|
|
624
|
+
if (step.type === "confirm") {
|
|
625
|
+
if (selectedChoice.value === "cancel") {
|
|
626
|
+
throw new Error("Prompt cancelled");
|
|
627
|
+
}
|
|
628
|
+
return sanitizeStateForInstall(state);
|
|
629
|
+
}
|
|
630
|
+
const nextValue = validateStepValue(step, state, selectedChoice.value);
|
|
631
|
+
setStateValue(state, step, nextValue);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
const nextSteps = buildSteps(state, repos);
|
|
635
|
+
if (stepIndex < nextSteps.length - 1) {
|
|
636
|
+
stepIndex += 1;
|
|
637
|
+
rerender();
|
|
638
|
+
return undefined;
|
|
639
|
+
}
|
|
640
|
+
return sanitizeStateForInstall(state);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
return undefined;
|
|
644
|
+
},
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
async function promptForChoice(rl, label, choices) {
|
|
106
649
|
console.log("");
|
|
107
|
-
console.log(
|
|
108
|
-
|
|
109
|
-
const summary =
|
|
110
|
-
console.log(` ${index + 1}. ${
|
|
650
|
+
console.log(label);
|
|
651
|
+
choices.forEach((choice, index) => {
|
|
652
|
+
const summary = choice.description ? ` - ${choice.description}` : "";
|
|
653
|
+
console.log(` ${index + 1}. ${choice.label}${summary}`);
|
|
111
654
|
});
|
|
112
655
|
console.log("");
|
|
113
656
|
|
|
114
657
|
while (true) {
|
|
115
|
-
const answer = await rl.question("Select
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
658
|
+
const answer = (await rl.question("Select option: ")).trim();
|
|
659
|
+
if (/^\d+$/.test(answer)) {
|
|
660
|
+
const index = Number.parseInt(answer, 10) - 1;
|
|
661
|
+
if (index >= 0 && index < choices.length) {
|
|
662
|
+
return choices[index].value;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
const exact = choices.find((choice) => choice.label === answer || choice.value === answer);
|
|
667
|
+
if (exact) {
|
|
668
|
+
return exact.value;
|
|
120
669
|
}
|
|
670
|
+
console.log(`Invalid selection: ${answer || "(empty)"}`);
|
|
121
671
|
}
|
|
122
672
|
}
|
|
123
673
|
|
|
124
|
-
async function
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
674
|
+
async function promptForMultiChoice(rl, label, choices, defaults) {
|
|
675
|
+
console.log("");
|
|
676
|
+
console.log(label);
|
|
677
|
+
console.log("Enter one or more labels separated by commas.");
|
|
678
|
+
choices.forEach((choice, index) => {
|
|
679
|
+
const summary = choice.description ? ` - ${choice.description}` : "";
|
|
680
|
+
console.log(` ${index + 1}. ${choice.label}${summary}`);
|
|
681
|
+
});
|
|
682
|
+
console.log("");
|
|
128
683
|
|
|
129
|
-
|
|
130
|
-
|
|
684
|
+
while (true) {
|
|
685
|
+
const answer = (await rl.question(`Providers [${defaults.join(",")}]: `)).trim();
|
|
686
|
+
const selected = normalizeProviders(
|
|
687
|
+
answer
|
|
688
|
+
? answer.split(",").map((entry) => entry.trim())
|
|
689
|
+
: defaults,
|
|
690
|
+
);
|
|
691
|
+
const invalid = selected.filter(
|
|
692
|
+
(provider) => !choices.some((choice) => choice.value === provider),
|
|
693
|
+
);
|
|
694
|
+
if (invalid.length === 0 && selected.length > 0) {
|
|
695
|
+
return selected;
|
|
696
|
+
}
|
|
697
|
+
console.log(`Invalid providers: ${invalid.join(", ")}`);
|
|
131
698
|
}
|
|
699
|
+
}
|
|
132
700
|
|
|
701
|
+
function printFallbackPlan(state, repos) {
|
|
702
|
+
const selectedRepo = resolveTemplateRepo(state.template, repos);
|
|
703
|
+
console.log("");
|
|
704
|
+
executionPreview(state, selectedRepo).forEach((line) => console.log(line));
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
async function promptLinearly(initialState, repos) {
|
|
708
|
+
const state = sanitizeStateForInstall(initialState);
|
|
133
709
|
const rl = createInterface({
|
|
134
710
|
input: process.stdin,
|
|
135
711
|
output: process.stdout,
|
|
136
712
|
});
|
|
137
713
|
|
|
138
714
|
try {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
715
|
+
const targetDirAnswer = await rl.question(`Project directory [${state.targetDir}]: `);
|
|
716
|
+
state.targetDir = targetDirAnswer.trim() || state.targetDir;
|
|
717
|
+
|
|
718
|
+
const defaultProjectName = inferProjectName(state.targetDir);
|
|
719
|
+
const projectNameAnswer = await rl.question(`Project name [${state.projectName || defaultProjectName}]: `);
|
|
720
|
+
state.projectName = projectNameAnswer.trim() || state.projectName || defaultProjectName;
|
|
721
|
+
|
|
722
|
+
state.githubAuthMode = await promptForChoice(rl, "GitHub auth mode", GITHUB_AUTH_CHOICES);
|
|
723
|
+
if (state.githubAuthMode === "pat") {
|
|
724
|
+
state.githubPat = (await rl.question("GitHub PAT: ")).trim();
|
|
725
|
+
if (!state.githubPat) {
|
|
726
|
+
throw new Error("GitHub PAT cannot be empty.");
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
state.template = await promptForChoice(rl, `Template repo from ${state.owner}`, buildTemplateChoices(repos));
|
|
731
|
+
state.appMode = await promptForChoice(rl, "App mode", APP_MODE_CHOICES);
|
|
732
|
+
state.aiProviders = await promptForMultiChoice(rl, "AI providers", AI_PROVIDER_CHOICES, state.aiProviders);
|
|
733
|
+
|
|
734
|
+
if (!state.force && directoryHasUserFiles(state.targetDir)) {
|
|
735
|
+
state.force = await promptForChoice(rl, "Target directory is not empty", [
|
|
736
|
+
{
|
|
737
|
+
label: "Continue",
|
|
738
|
+
value: true,
|
|
739
|
+
description: "Allow scaffolding into the existing directory",
|
|
740
|
+
},
|
|
741
|
+
{
|
|
742
|
+
label: "Cancel",
|
|
743
|
+
value: false,
|
|
744
|
+
description: "Stop without changing files",
|
|
745
|
+
},
|
|
746
|
+
]);
|
|
747
|
+
if (!state.force) {
|
|
748
|
+
throw new Error("Prompt cancelled");
|
|
144
749
|
}
|
|
145
750
|
}
|
|
146
751
|
|
|
147
|
-
|
|
148
|
-
|
|
752
|
+
printFallbackPlan(state, repos);
|
|
753
|
+
const confirmation = await promptForChoice(rl, "Run scaffold now?", CONFIRM_CHOICES);
|
|
754
|
+
if (confirmation !== "proceed") {
|
|
755
|
+
throw new Error("Prompt cancelled");
|
|
149
756
|
}
|
|
150
757
|
} finally {
|
|
151
758
|
rl.close();
|
|
152
759
|
}
|
|
153
760
|
|
|
154
|
-
return
|
|
761
|
+
return sanitizeStateForInstall(state);
|
|
155
762
|
}
|
|
156
763
|
|
|
157
764
|
function printSuccess(result) {
|
|
@@ -172,6 +779,25 @@ function printSuccess(result) {
|
|
|
172
779
|
}
|
|
173
780
|
}
|
|
174
781
|
|
|
782
|
+
function validateNonInteractiveState(state) {
|
|
783
|
+
if (!state.targetDir) {
|
|
784
|
+
throw new Error("`--yes` requires a target directory.");
|
|
785
|
+
}
|
|
786
|
+
if (!state.template) {
|
|
787
|
+
throw new Error("`--yes` requires a template repo.");
|
|
788
|
+
}
|
|
789
|
+
if (!state.projectName) {
|
|
790
|
+
state.projectName = inferProjectName(state.targetDir);
|
|
791
|
+
}
|
|
792
|
+
if (state.githubAuthMode === "pat" && !state.githubPat) {
|
|
793
|
+
throw new Error("`--github-auth pat` requires `--github-pat`.");
|
|
794
|
+
}
|
|
795
|
+
if (!Array.isArray(state.aiProviders) || state.aiProviders.length === 0) {
|
|
796
|
+
state.aiProviders = [...DEFAULT_AI_PROVIDERS];
|
|
797
|
+
}
|
|
798
|
+
return state;
|
|
799
|
+
}
|
|
800
|
+
|
|
175
801
|
async function main() {
|
|
176
802
|
let options;
|
|
177
803
|
try {
|
|
@@ -203,18 +829,29 @@ async function main() {
|
|
|
203
829
|
}
|
|
204
830
|
|
|
205
831
|
try {
|
|
206
|
-
const
|
|
832
|
+
const state = hydrateOptions(options);
|
|
833
|
+
applyRuntimeGitHubAuth(state);
|
|
834
|
+
const repos = await fetchTemplateRepos({ owner: state.owner });
|
|
207
835
|
if (repos.length === 0) {
|
|
208
|
-
throw new Error(`No public template-* repos found for ${
|
|
836
|
+
throw new Error(`No public template-* repos found for ${state.owner}.`);
|
|
209
837
|
}
|
|
210
838
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
839
|
+
const resolvedState = options.yes
|
|
840
|
+
? validateNonInteractiveState(sanitizeStateForInstall(state))
|
|
841
|
+
: process.stdin.isTTY && process.stdout.isTTY
|
|
842
|
+
? await runWizard(state, repos)
|
|
843
|
+
: await promptLinearly(state, repos);
|
|
844
|
+
|
|
845
|
+
applyRuntimeGitHubAuth(resolvedState);
|
|
846
|
+
const selectedRepo = resolveTemplateRepo(resolvedState.template, repos);
|
|
847
|
+
const destinationRoot = ensureTargetDir(resolvedState.targetDir, {
|
|
848
|
+
force: resolvedState.force,
|
|
849
|
+
});
|
|
214
850
|
const result = installTemplateRepo({
|
|
215
851
|
destinationRoot: path.resolve(destinationRoot),
|
|
216
852
|
templateRepo: selectedRepo,
|
|
217
|
-
|
|
853
|
+
setupSelections: resolvedState,
|
|
854
|
+
skipBootstrap: resolvedState.skipBootstrap,
|
|
218
855
|
});
|
|
219
856
|
printSuccess(result);
|
|
220
857
|
} catch (error) {
|