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