create-cogenta 0.1.6 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/blueprint-defaults.d.ts +58 -0
  2. package/dist/blueprint-defaults.d.ts.map +1 -0
  3. package/dist/blueprint-defaults.js +111 -0
  4. package/dist/blueprint-defaults.js.map +1 -0
  5. package/dist/blueprints/association.d.ts.map +1 -1
  6. package/dist/blueprints/association.js +50 -1
  7. package/dist/blueprints/association.js.map +1 -1
  8. package/dist/blueprints/blog.d.ts.map +1 -1
  9. package/dist/blueprints/blog.js +26 -0
  10. package/dist/blueprints/blog.js.map +1 -1
  11. package/dist/blueprints/content-pack.d.ts +9 -1
  12. package/dist/blueprints/content-pack.d.ts.map +1 -1
  13. package/dist/blueprints/content-pack.js +18 -0
  14. package/dist/blueprints/content-pack.js.map +1 -1
  15. package/dist/blueprints/documentation.d.ts.map +1 -1
  16. package/dist/blueprints/documentation.js +45 -1
  17. package/dist/blueprints/documentation.js.map +1 -1
  18. package/dist/blueprints/magazine.d.ts.map +1 -1
  19. package/dist/blueprints/magazine.js +34 -0
  20. package/dist/blueprints/magazine.js.map +1 -1
  21. package/dist/blueprints/portfolio.d.ts.map +1 -1
  22. package/dist/blueprints/portfolio.js +26 -0
  23. package/dist/blueprints/portfolio.js.map +1 -1
  24. package/dist/blueprints/restaurant.d.ts.map +1 -1
  25. package/dist/blueprints/restaurant.js +50 -1
  26. package/dist/blueprints/restaurant.js.map +1 -1
  27. package/dist/blueprints/saas.d.ts.map +1 -1
  28. package/dist/blueprints/saas.js +50 -1
  29. package/dist/blueprints/saas.js.map +1 -1
  30. package/dist/blueprints/vitrine.d.ts.map +1 -1
  31. package/dist/blueprints/vitrine.js +36 -0
  32. package/dist/blueprints/vitrine.js.map +1 -1
  33. package/dist/config-file.d.ts.map +1 -1
  34. package/dist/config-file.js +22 -0
  35. package/dist/config-file.js.map +1 -1
  36. package/dist/document-step.d.ts +35 -0
  37. package/dist/document-step.d.ts.map +1 -0
  38. package/dist/document-step.js +53 -0
  39. package/dist/document-step.js.map +1 -0
  40. package/dist/plan-flow.d.ts +51 -0
  41. package/dist/plan-flow.d.ts.map +1 -0
  42. package/dist/plan-flow.js +77 -0
  43. package/dist/plan-flow.js.map +1 -0
  44. package/dist/recap.d.ts +3 -0
  45. package/dist/recap.d.ts.map +1 -1
  46. package/dist/recap.js +26 -2
  47. package/dist/recap.js.map +1 -1
  48. package/dist/scaffold.d.ts +37 -0
  49. package/dist/scaffold.d.ts.map +1 -1
  50. package/dist/scaffold.js +113 -9
  51. package/dist/scaffold.js.map +1 -1
  52. package/dist/skin-flow.d.ts +22 -8
  53. package/dist/skin-flow.d.ts.map +1 -1
  54. package/dist/skin-flow.js +57 -29
  55. package/dist/skin-flow.js.map +1 -1
  56. package/dist/types.d.ts +8 -0
  57. package/dist/types.d.ts.map +1 -1
  58. package/dist/types.js.map +1 -1
  59. package/dist/wizard.d.ts +19 -3
  60. package/dist/wizard.d.ts.map +1 -1
  61. package/dist/wizard.js +254 -45
  62. package/dist/wizard.js.map +1 -1
  63. package/package.json +9 -9
package/dist/skin-flow.js CHANGED
@@ -1,55 +1,83 @@
1
1
  import { mkdir, writeFile } from 'node:fs/promises';
2
2
  import { join } from 'node:path';
3
- import { generateSkin } from '@cogenta/agents';
3
+ import { generateSkinCandidates } from '@cogenta/agents';
4
4
  import { renderSkinPreview } from './skin-preview.js';
5
5
  const DEFAULT_MAX_REGENERATIONS = 3;
6
- async function writePreview(tokens, siteName, targetDir) {
7
- const previewDir = join(targetDir, '.cogenta', 'skin-preview');
8
- await mkdir(previewDir, { recursive: true });
9
- const pages = renderSkinPreview(tokens, siteName);
10
- await Promise.all(pages.map((page) => writeFile(join(previewDir, page.filename), page.html, 'utf8')));
11
- return previewDir;
6
+ async function writePreviews(candidates, siteName, targetDir) {
7
+ const root = join(targetDir, '.cogenta', 'skin-preview');
8
+ await mkdir(root, { recursive: true });
9
+ await Promise.all(candidates.map(async (candidate) => {
10
+ const directory = join(root, candidate.id);
11
+ await mkdir(directory, { recursive: true });
12
+ const pages = renderSkinPreview(candidate.tokens, siteName);
13
+ await Promise.all(pages.map((page) => writeFile(join(directory, page.filename), page.html, 'utf8')));
14
+ }));
15
+ return root;
12
16
  }
13
17
  /**
14
- * Step 5 of L9 task 7's pipeline: "Aperçu proposé sur trois pages types, avec
15
- * possibilité de régénérer ou d'ajuster." Each round runs `generateSkin`'s own
16
- * three-attempt auto-correction loop; on success, three real preview pages are
17
- * written to disk (`.cogenta/skin-preview/`) and the human is offered accept,
18
- * regenerate, or fall back to the default — bounded so a non-interactive
19
- * `--yes`/`--config` run (whose `Prompter.choice` always returns its default,
20
- * "accept") never loops.
18
+ * L19 task 3, in the installer: "l'utilisateur voit et choisit explicitement
19
+ * entre deux et cinq gabarits proposés, jamais un seul choix imposé."
20
+ *
21
+ * The shape of the round is what it was in L9 task 7 — generate, preview on
22
+ * three real pages, then accept / regenerate / fall back to the default,
23
+ * bounded so a non-interactive `--yes` run (whose `Prompter.choice` always
24
+ * answers with its default) never loops. What changed is that a round now
25
+ * produces several designs rather than one, each validated against contract
26
+ * D by `generateSkin`'s own loop, and the accept step is a pick among them.
27
+ *
28
+ * A round that cannot produce at least two distinct valid designs is not
29
+ * offered as a choice: it falls through to the next round, and eventually to
30
+ * the default skin, saying why. Presenting one option as if it were a
31
+ * decision is the failure mode this task exists to remove.
21
32
  */
22
33
  export async function chooseSkin(options) {
23
34
  const maxRegenerations = options.maxRegenerations ?? DEFAULT_MAX_REGENERATIONS;
24
35
  let lastReason = 'no attempt was made';
25
36
  for (let round = 1; round <= maxRegenerations; round++) {
26
- options.out.detail(`Generating a skin from your description (round ${round}/${maxRegenerations})…`);
27
- const result = await generateSkin({
37
+ options.out.detail(`Generating design proposals from your description (round ${round}/${maxRegenerations})…`);
38
+ const result = await generateSkinCandidates({
28
39
  client: options.client,
29
40
  model: options.model,
30
41
  description: options.description,
31
42
  blueprintLabel: options.blueprintLabel,
43
+ ...(options.candidateCount === undefined ? {} : { count: options.candidateCount }),
32
44
  });
45
+ for (const failure of result.failures) {
46
+ options.out.warn(`The "${failure.label}" design was not offered: ${failure.reason}.`);
47
+ }
33
48
  if (!result.ok) {
34
- lastReason = `${result.attempts} attempt${result.attempts === 1 ? '' : 's'} all failed validation: ${result.reason}`;
49
+ lastReason = `no usable choice of designs was produced: ${result.reason}`;
35
50
  options.out.warn(lastReason);
36
51
  break;
37
52
  }
38
- const previewDir = await writePreview(result.tokens, options.siteName, options.targetDir);
39
- options.out.ok(`Skin generated and validated in ${result.attempts} attempt${result.attempts === 1 ? '' : 's'}. Preview: ${previewDir}`);
40
- const choice = await options.prompter.choice('Use this generated skin?', [
41
- { label: 'Accept it', value: 'accept' },
42
- { label: 'Regenerate (try again)', value: 'regenerate' },
43
- { label: 'Use the default skin instead', value: 'default' },
44
- ], 0);
45
- if (choice === 'accept') {
46
- return { kind: 'generated', tokens: result.tokens, attempts: result.attempts };
53
+ const previewRoot = await writePreviews(result.candidates, options.siteName, options.targetDir);
54
+ options.out.ok(`${result.candidates.length} designs generated and validated. Previews: ${previewRoot}`);
55
+ for (const candidate of result.candidates) {
56
+ options.out.detail(` ${candidate.label} ${previewRoot}${join('', candidate.id)}`);
47
57
  }
58
+ const choice = await options.prompter.choice('Which design do you want? (open the previews above to compare)', [
59
+ ...result.candidates.map((candidate) => ({
60
+ label: candidate.label,
61
+ value: { kind: 'candidate', candidate },
62
+ hint: candidate.rationale,
63
+ })),
64
+ { label: 'None of these — propose new ones', value: 'regenerate' },
65
+ { label: 'None of these — use the default skin', value: 'default' },
66
+ ], 0);
48
67
  if (choice === 'default') {
49
- return { kind: 'default', reason: 'the default skin was chosen over the generated one' };
68
+ return { kind: 'default', reason: 'the default skin was chosen over the generated ones' };
69
+ }
70
+ if (choice !== 'regenerate') {
71
+ return {
72
+ kind: 'generated',
73
+ tokens: choice.candidate.tokens,
74
+ attempts: choice.candidate.attempts,
75
+ candidateId: choice.candidate.id,
76
+ candidateLabel: choice.candidate.label,
77
+ offered: result.candidates.length,
78
+ };
50
79
  }
51
- // 'regenerate' loop again.
52
- lastReason = `${maxRegenerations} regeneration round${maxRegenerations === 1 ? '' : 's'} were used without accepting a result`;
80
+ lastReason = `${maxRegenerations} regeneration round${maxRegenerations === 1 ? '' : 's'} were used without picking a design`;
53
81
  }
54
82
  return { kind: 'default', reason: lastReason };
55
83
  }
@@ -1 +1 @@
1
- {"version":3,"file":"skin-flow.js","sourceRoot":"","sources":["../src/skin-flow.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"skin-flow.js","sourceRoot":"","sources":["../src/skin-flow.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -14,6 +14,14 @@ export interface WizardAnswers {
14
14
  /** Free text (sector, mood, audience, brand colours) driving AI skin generation (L9 task 7). Empty or absent: generation is skipped, the default skin is used. */
15
15
  readonly siteDescription?: string;
16
16
  readonly adminEmail: string;
17
+ /**
18
+ * Specification documents to read before anything else (L19 task 6).
19
+ * Absent — which is the default everywhere, including `--yes` — and the
20
+ * installer behaves exactly as it did before L19 existed (R2).
21
+ */
22
+ readonly documentPaths?: readonly string[];
23
+ /** Answers to L19 task 8's per-site-type recommendations, by setting id. Unanswered ids fall back to the recommendation. */
24
+ readonly blueprintSettings?: Readonly<Record<string, boolean>>;
17
25
  }
18
26
  export declare function defaultAnswers(targetDir: string, siteName: string): WizardAnswers;
19
27
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,yGAAyG;AACzG,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,cAAc,EAAE,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAA;IACxD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,WAAW,EAAE,aAAa,CAAA;IACnC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,kKAAkK;IAClK,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAA;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAC5B;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,aAAa,CAWjF"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,yGAAyG;AACzG,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,cAAc,EAAE,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAA;IACxD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,WAAW,EAAE,aAAa,CAAA;IACnC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,kKAAkK;IAClK,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAA;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC1C,4HAA4H;IAC5H,QAAQ,CAAC,iBAAiB,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;CAC/D;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,aAAa,CAWjF"}
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAmBA,MAAM,UAAU,cAAc,CAAC,SAAiB,EAAE,QAAgB;IAChE,OAAO;QACL,SAAS;QACT,QAAQ;QACR,OAAO,EAAE,uBAAuB;QAChC,aAAa,EAAE,IAAI;QACnB,WAAW,EAAE,OAAO;QACpB,cAAc,EAAE,QAAQ;QACxB,WAAW,EAAE,MAAM;QACnB,UAAU,EAAE,mBAAmB;KAChC,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AA2BA,MAAM,UAAU,cAAc,CAAC,SAAiB,EAAE,QAAgB;IAChE,OAAO;QACL,SAAS;QACT,QAAQ;QACR,OAAO,EAAE,uBAAuB;QAChC,aAAa,EAAE,IAAI;QACnB,WAAW,EAAE,OAAO;QACpB,cAAc,EAAE,QAAQ;QACxB,WAAW,EAAE,MAAM;QACnB,UAAU,EAAE,mBAAmB;KAChC,CAAA;AACH,CAAC"}
package/dist/wizard.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { SitePlanDraft } from '@cogenta/agents';
1
2
  import type { Output } from '@cogenta/cli';
2
3
  import type { Prompter } from './prompts.js';
3
4
  export interface RunWizardOptions {
@@ -7,11 +8,26 @@ export interface RunWizardOptions {
7
8
  readonly out: Output;
8
9
  readonly configPath?: string;
9
10
  readonly env?: Record<string, string | undefined>;
11
+ /** Where a relative document path typed by the user is resolved from. Defaults to `process.cwd()`. */
12
+ readonly cwd?: string;
13
+ /**
14
+ * Test seam only — the same one `llm-setup.ts` already carries. It lets a
15
+ * test drive the whole installer, document step included, without a key
16
+ * and without the network, rather than mocking a shared module.
17
+ */
18
+ readonly fetchImpl?: typeof fetch;
10
19
  }
11
20
  /**
12
- * The whole ten-step wizard, orchestrated. Returns a process exit code: `0`
13
- * a working site, `1` the environment or the install failed, `2` a `--config`
14
- * file was invalid.
21
+ * The whole wizard, orchestrated. Returns a process exit code: `0` a working
22
+ * site, `1` the environment or the install failed, `2` a `--config` file was
23
+ * invalid.
24
+ *
25
+ * L19 inserts one optional step ahead of everything else — read a
26
+ * specification document, propose a plan, let a human approve it item by
27
+ * item — and changes nothing about the run that declines it. With no
28
+ * provider configured, or with `--yes`, that step is never entered and the
29
+ * site produced is the one this installer produced before L19 existed.
15
30
  */
16
31
  export declare function runWizard(options: RunWizardOptions): Promise<number>;
32
+ export type { SitePlanDraft };
17
33
  //# sourceMappingURL=wizard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"wizard.d.ts","sourceRoot":"","sources":["../src/wizard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAW1C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAM5C,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAA;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;CAClD;AAsED;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAkH1E"}
1
+ {"version":3,"file":"wizard.d.ts","sourceRoot":"","sources":["../src/wizard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAqB,aAAa,EAAE,MAAM,iBAAiB,CAAA;AACvE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAkB1C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAM5C,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAA;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;IACjD,sGAAsG;IACtG,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;IACrB;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;CAClC;AAkLD;;;;;;;;;;GAUG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CA+K1E;AAsGD,YAAY,EAAE,aAAa,EAAE,CAAA"}
package/dist/wizard.js CHANGED
@@ -1,35 +1,91 @@
1
+ import { blueprintSettings, inferBlueprint, resolveBlueprintSettings, } from './blueprint-defaults.js';
1
2
  import { BLUEPRINT_CONTENT_PACKS } from './blueprints/content-packs.js';
2
3
  import { BLUEPRINTS, resolveBlueprint } from './blueprints/registry.js';
3
4
  import { loadConfigFile } from './config-file.js';
5
+ import { collectDocuments, readDocuments } from './document-step.js';
4
6
  import { checkEnvironment } from './environment.js';
5
7
  import { createProviderClient, LLM_PROVIDERS, validateApiKey, } from './llm-setup.js';
8
+ import { runPlanFlow } from './plan-flow.js';
6
9
  import { printRecap } from './recap.js';
7
10
  import { scaffoldSite } from './scaffold.js';
8
11
  import { chooseSkin } from './skin-flow.js';
9
12
  import { defaultAnswers } from './types.js';
10
- async function collectAnswers(prompter, targetDir, siteName, detectedDatabases) {
13
+ /**
14
+ * The provider question, asked before everything else now.
15
+ *
16
+ * It moved for one reason: L19's document step needs a model, and the lot
17
+ * puts that step "avant même de poser les questions actuelles". Nothing else
18
+ * changed about it — `none` is still the default, still the first option,
19
+ * and answering it still leaves an installer that asks nothing further about
20
+ * AI and produces the same site it always did (R2).
21
+ */
22
+ async function collectLlmSetup(prompter, fetchImpl) {
23
+ const provider = await prompter.choice('LLM provider (optional — Cogenta works fully without one)', LLM_PROVIDERS.map((entry) => ({ label: entry.label, value: entry.id })), 0);
24
+ if (provider === 'none')
25
+ return { provider };
26
+ const option = LLM_PROVIDERS.find((entry) => entry.id === provider);
27
+ const model = await prompter.text('Model', option?.defaultModel ?? '');
28
+ const apiKey = await prompter.text('API key', '');
29
+ if (apiKey === '')
30
+ return { provider, model };
31
+ const validation = await validateApiKey({
32
+ provider,
33
+ apiKey,
34
+ model,
35
+ ...(fetchImpl === undefined ? {} : { fetchImpl }),
36
+ });
37
+ return { provider, model, apiKey, validation };
38
+ }
39
+ async function collectAnswers(prompter, out, targetDir, siteName, detectedDatabases, llm, prefill) {
11
40
  const defaults = defaultAnswers(targetDir, siteName);
41
+ if (prefill.because.length > 0) {
42
+ out.heading('Pre-filled from your document');
43
+ out.line('Every answer below is only a suggestion. Change any of them.');
44
+ for (const line of prefill.because)
45
+ out.detail(line);
46
+ }
12
47
  const name = await prompter.text('Site name', defaults.siteName);
13
48
  const url = await prompter.text('Site URL', defaults.siteUrl);
14
- const locale = await prompter.text('Primary language (locale code)', defaults.defaultLocale);
15
- const blueprintId = await prompter.choice('Site type', BLUEPRINTS.map((entry) => ({ label: entry.label, value: entry.id })), 0);
49
+ const locale = await prompter.text('Primary language (locale code)', prefill.defaultLocale ?? defaults.defaultLocale);
50
+ const blueprintIndex = Math.max(0, BLUEPRINTS.findIndex((entry) => entry.id === (prefill.blueprintId ?? defaults.blueprintId)));
51
+ const blueprintId = await prompter.choice('Site type', BLUEPRINTS.map((entry) => ({ label: entry.label, value: entry.id })), blueprintIndex);
52
+ // Postgres and MySQL are always offered, not only when a local server is
53
+ // detected — a real site is as likely to point at a managed remote
54
+ // database (Neon, PlanetScale, a hosting provider's MySQL) as at one
55
+ // running on this machine, and hiding the option entirely made those two
56
+ // of the three supported drivers unreachable from the wizard.
16
57
  const databaseChoices = [
17
- { label: 'SQLite (default — you can change this later)', value: 'sqlite' },
18
- ...(detectedDatabases.postgres
19
- ? [{ label: 'Postgres (detected)', value: 'postgres' }]
20
- : []),
21
- ...(detectedDatabases.mysql ? [{ label: 'MySQL (detected)', value: 'mysql' }] : []),
58
+ { label: 'SQLite (default — no separate database server needed)', value: 'sqlite' },
59
+ {
60
+ label: detectedDatabases.postgres ? 'Postgres (detected locally)' : 'Postgres',
61
+ value: 'postgres',
62
+ },
63
+ { label: detectedDatabases.mysql ? 'MySQL (detected locally)' : 'MySQL', value: 'mysql' },
22
64
  ];
23
65
  const databaseDriver = await prompter.choice('Database', databaseChoices, 0);
24
- const llmChoice = await prompter.choice('LLM provider', LLM_PROVIDERS.map((entry) => ({ label: entry.label, value: entry.id })), 0);
25
- let llmModel;
26
- let llmApiKey;
27
- let siteDescription;
28
- if (llmChoice !== 'none') {
29
- const option = LLM_PROVIDERS.find((entry) => entry.id === llmChoice);
30
- llmModel = await prompter.text('Model', option?.defaultModel ?? '');
31
- llmApiKey = await prompter.text('API key', '');
32
- siteDescription = await prompter.text('Site description for AI skin generation (sector, mood, audience, brand colours — leave blank to skip and keep the default skin)', '');
66
+ let databaseUrl;
67
+ if (databaseDriver === 'postgres' && !detectedDatabases.postgres) {
68
+ databaseUrl = await prompter.text('Postgres connection URL', '');
69
+ }
70
+ else if (databaseDriver === 'mysql' && !detectedDatabases.mysql) {
71
+ databaseUrl = await prompter.text('MySQL connection URL', '');
72
+ }
73
+ // Only asked when no document already answered it: a plan carries its own
74
+ // designs, generated from the brief, and asking for the same information
75
+ // twice is how an installer earns its reputation.
76
+ let siteDescription = prefill.siteDescription;
77
+ if (llm.provider !== 'none' && prefill.siteDescription === undefined) {
78
+ siteDescription = await prompter.text('Site description for AI design generation (sector, mood, audience, brand colours — leave blank to skip and keep the default skin)', '');
79
+ }
80
+ // L19 task 8 — the per-site-type recommendations, confirmed one at a time.
81
+ const recommendations = blueprintSettings(blueprintId);
82
+ const settingAnswers = {};
83
+ if (recommendations.settings.length > 0) {
84
+ out.heading('Defaults for this kind of site');
85
+ for (const setting of recommendations.settings) {
86
+ out.detail(setting.why);
87
+ settingAnswers[setting.id] = await prompter.confirm(setting.question, setting.recommended);
88
+ }
33
89
  }
34
90
  const adminEmail = await prompter.text('Admin email', defaults.adminEmail);
35
91
  return {
@@ -39,21 +95,50 @@ async function collectAnswers(prompter, targetDir, siteName, detectedDatabases)
39
95
  defaultLocale: locale,
40
96
  blueprintId,
41
97
  databaseDriver,
42
- llmProvider: llmChoice,
43
- ...(llmModel === undefined || llmModel === '' ? {} : { llmModel }),
44
- ...(llmApiKey === undefined || llmApiKey === '' ? {} : { llmApiKey }),
98
+ ...(databaseUrl === undefined || databaseUrl === '' ? {} : { databaseUrl }),
99
+ llmProvider: llm.provider,
100
+ ...(llm.model === undefined || llm.model === '' ? {} : { llmModel: llm.model }),
101
+ ...(llm.apiKey === undefined || llm.apiKey === '' ? {} : { llmApiKey: llm.apiKey }),
45
102
  ...(siteDescription === undefined || siteDescription === '' ? {} : { siteDescription }),
46
103
  adminEmail,
104
+ blueprintSettings: settingAnswers,
105
+ };
106
+ }
107
+ function prefillFrom(plan) {
108
+ if (plan === undefined || plan.kind === 'failed')
109
+ return { because: [] };
110
+ const brief = plan.draft.brief;
111
+ const locale = (plan.kind === 'reviewed' ? plan.approved.locales[0] : undefined) ?? brief.languages[0];
112
+ const blueprintId = inferBlueprint(brief);
113
+ const because = [];
114
+ if (locale !== undefined)
115
+ because.push(`Language “${locale}” — from the brief.`);
116
+ if (blueprintId !== undefined) {
117
+ because.push(`Site type “${blueprintId}” — matched from what the brief describes.`);
118
+ }
119
+ because.push(`Design description — the brief's own summary.`);
120
+ return {
121
+ ...(locale === undefined ? {} : { defaultLocale: locale }),
122
+ ...(blueprintId === undefined ? {} : { blueprintId }),
123
+ siteDescription: brief.summary,
124
+ because,
47
125
  };
48
126
  }
49
127
  /**
50
- * The whole ten-step wizard, orchestrated. Returns a process exit code: `0`
51
- * a working site, `1` the environment or the install failed, `2` a `--config`
52
- * file was invalid.
128
+ * The whole wizard, orchestrated. Returns a process exit code: `0` a working
129
+ * site, `1` the environment or the install failed, `2` a `--config` file was
130
+ * invalid.
131
+ *
132
+ * L19 inserts one optional step ahead of everything else — read a
133
+ * specification document, propose a plan, let a human approve it item by
134
+ * item — and changes nothing about the run that declines it. With no
135
+ * provider configured, or with `--yes`, that step is never entered and the
136
+ * site produced is the one this installer produced before L19 existed.
53
137
  */
54
138
  export async function runWizard(options) {
55
139
  const { out, prompter } = options;
56
140
  const env = options.env ?? process.env;
141
+ const cwd = options.cwd ?? process.cwd();
57
142
  const environment = await checkEnvironment({ targetDir: options.targetDir, env });
58
143
  out.heading('Environment');
59
144
  for (const check of environment.checks) {
@@ -71,6 +156,8 @@ export async function runWizard(options) {
71
156
  return 1;
72
157
  }
73
158
  let answers;
159
+ let llm;
160
+ let plan;
74
161
  if (options.configPath !== undefined) {
75
162
  try {
76
163
  answers = await loadConfigFile(options.configPath, options.targetDir);
@@ -80,40 +167,61 @@ export async function runWizard(options) {
80
167
  prompter.close();
81
168
  return 2;
82
169
  }
170
+ llm = {
171
+ provider: answers.llmProvider,
172
+ ...(answers.llmModel === undefined ? {} : { model: answers.llmModel }),
173
+ ...(answers.llmApiKey === undefined ? {} : { apiKey: answers.llmApiKey }),
174
+ };
175
+ if (llm.provider !== 'none' && llm.apiKey !== undefined && llm.apiKey !== '') {
176
+ llm = {
177
+ ...llm,
178
+ validation: await validateApiKey({
179
+ provider: llm.provider,
180
+ apiKey: llm.apiKey,
181
+ model: llm.model ?? '',
182
+ ...(options.fetchImpl === undefined ? {} : { fetchImpl: options.fetchImpl }),
183
+ }),
184
+ };
185
+ }
186
+ plan = await runDocumentStepFromConfig({
187
+ answers,
188
+ llm,
189
+ out,
190
+ cwd,
191
+ ...(options.fetchImpl === undefined ? {} : { fetchImpl: options.fetchImpl }),
192
+ });
83
193
  }
84
194
  else {
85
- answers = await collectAnswers(prompter, options.targetDir, options.siteName, {
195
+ llm = await collectLlmSetup(prompter, options.fetchImpl);
196
+ plan = await runDocumentStepInteractively({
197
+ llm,
198
+ prompter,
199
+ out,
200
+ cwd,
201
+ ...(options.fetchImpl === undefined ? {} : { fetchImpl: options.fetchImpl }),
202
+ });
203
+ answers = await collectAnswers(prompter, out, options.targetDir, options.siteName, {
86
204
  postgres: environment.detectedDatabases.includes('postgres'),
87
205
  mysql: environment.detectedDatabases.includes('mysql'),
88
- });
206
+ }, llm, prefillFrom(plan));
89
207
  }
90
208
  const resolvedBlueprint = resolveBlueprint(answers.blueprintId);
91
- let keyValidation;
92
- let llmModel = '';
93
- if (answers.llmProvider !== 'none' &&
94
- answers.llmApiKey !== undefined &&
95
- answers.llmApiKey !== '') {
96
- const option = LLM_PROVIDERS.find((entry) => entry.id === answers.llmProvider);
97
- llmModel = answers.llmModel ?? option?.defaultModel ?? '';
98
- keyValidation = await validateApiKey({
99
- provider: answers.llmProvider,
100
- apiKey: answers.llmApiKey,
101
- model: llmModel,
102
- });
103
- }
104
- // "Aperçu proposé sur trois pages types, avec possibilité de régénérer ou
105
- // d'ajuster" (L9 task 7) needs the prompter, so it stays open until here —
106
- // only a blueprint with a real content pack writes a `theme.tokens.json` a
107
- // generated skin could replace.
209
+ const keyValidation = llm.validation;
210
+ const llmModel = llm.model ?? '';
211
+ // A reviewed plan already carries the design its owner picked, so the
212
+ // separate skin round is skipped rather than asking the same question a
213
+ // second time. Everything else about it is unchanged from L9 task 7.
214
+ const approvedSkin = plan?.kind === 'reviewed' ? plan.approved.skin : undefined;
108
215
  let skinOutcome;
109
- if (BLUEPRINT_CONTENT_PACKS[resolvedBlueprint.blueprint.id] !== undefined &&
216
+ if (approvedSkin === undefined &&
217
+ BLUEPRINT_CONTENT_PACKS[resolvedBlueprint.blueprint.id] !== undefined &&
110
218
  answers.llmProvider !== 'none' &&
111
219
  answers.llmApiKey !== undefined &&
112
220
  answers.llmApiKey !== '' &&
113
221
  keyValidation?.valid === true &&
114
222
  answers.siteDescription !== undefined &&
115
223
  answers.siteDescription !== '') {
116
- out.heading('Skin generation');
224
+ out.heading('Design proposals');
117
225
  skinOutcome = await chooseSkin({
118
226
  prompter,
119
227
  out,
@@ -121,6 +229,7 @@ export async function runWizard(options) {
121
229
  provider: answers.llmProvider,
122
230
  apiKey: answers.llmApiKey,
123
231
  model: llmModel,
232
+ ...(options.fetchImpl === undefined ? {} : { fetchImpl: options.fetchImpl }),
124
233
  }),
125
234
  model: llmModel,
126
235
  description: answers.siteDescription,
@@ -130,11 +239,17 @@ export async function runWizard(options) {
130
239
  });
131
240
  }
132
241
  prompter.close();
242
+ const confirmed = resolveBlueprintSettings(resolvedBlueprint.blueprint.id, answers.blueprintSettings ?? {});
243
+ const approved = plan?.kind === 'reviewed' ? plan.approved : undefined;
244
+ const locales = approved !== undefined && approved.locales.length > 0
245
+ ? [...new Set([answers.defaultLocale, ...approved.locales])]
246
+ : [answers.defaultLocale];
133
247
  const scaffold = await scaffoldSite({
134
248
  targetDir: answers.targetDir,
135
249
  siteName: answers.siteName,
136
250
  siteUrl: answers.siteUrl,
137
251
  defaultLocale: answers.defaultLocale,
252
+ locales,
138
253
  databaseDriver: answers.databaseDriver,
139
254
  ...(answers.databaseUrl === undefined ? {} : { databaseUrl: answers.databaseUrl }),
140
255
  ...(answers.llmProvider === 'none'
@@ -142,7 +257,24 @@ export async function runWizard(options) {
142
257
  : { llm: { provider: answers.llmProvider, model: answers.llmModel ?? '' } }),
143
258
  adminEmail: answers.adminEmail,
144
259
  blueprintId: resolvedBlueprint.blueprint.id,
145
- ...(skinOutcome?.kind === 'generated' ? { skinTokens: skinOutcome.tokens } : {}),
260
+ security: { pageMaxAge: confirmed.pageMaxAge, hstsMaxAge: confirmed.hstsMaxAge },
261
+ seedDemoContent: confirmed.seedDemoContent,
262
+ ...(approvedSkin === undefined
263
+ ? skinOutcome?.kind === 'generated'
264
+ ? { skinTokens: skinOutcome.tokens }
265
+ : {}
266
+ : { skinTokens: approvedSkin }),
267
+ ...(approved === undefined
268
+ ? {}
269
+ : {
270
+ approvedCollections: approved.collections,
271
+ approvedDemoContent: approved.demoContent,
272
+ }),
273
+ // A proposal nobody reviewed is stored, never applied.
274
+ ...(plan?.kind === 'deferred' ? { sitePlan: { draft: plan.draft } } : {}),
275
+ ...(plan?.kind === 'reviewed'
276
+ ? { sitePlan: { draft: plan.draft, decisions: plan.decisions } }
277
+ : {}),
146
278
  }, env);
147
279
  printRecap({
148
280
  answers,
@@ -151,7 +283,84 @@ export async function runWizard(options) {
151
283
  scaffold,
152
284
  ...(keyValidation === undefined ? {} : { keyValidation }),
153
285
  ...(skinOutcome === undefined ? {} : { skinOutcome }),
286
+ ...(plan === undefined ? {} : { plan }),
154
287
  }, out);
155
288
  return scaffold.migrateExitCode === 0 && scaffold.usersExitCode === 0 ? 0 : 1;
156
289
  }
290
+ function canPlan(llm) {
291
+ return (llm.provider !== 'none' &&
292
+ llm.apiKey !== undefined &&
293
+ llm.apiKey !== '' &&
294
+ llm.validation?.valid === true);
295
+ }
296
+ async function planFrom(input) {
297
+ if (input.documents.length === 0 || input.llm.provider === 'none')
298
+ return undefined;
299
+ const provider = input.llm.provider;
300
+ return runPlanFlow({
301
+ prompter: input.prompter,
302
+ out: input.out,
303
+ client: createProviderClient({
304
+ provider,
305
+ apiKey: input.llm.apiKey ?? '',
306
+ model: input.llm.model ?? '',
307
+ ...(input.fetchImpl === undefined ? {} : { fetchImpl: input.fetchImpl }),
308
+ }),
309
+ model: input.llm.model ?? '',
310
+ documents: input.documents,
311
+ });
312
+ }
313
+ async function runDocumentStepInteractively(input) {
314
+ // Never even asked without a working provider: a question whose only
315
+ // possible answer is "no" is noise, and R2 means that answer must remain
316
+ // a complete, unremarkable path through this installer.
317
+ if (!canPlan(input.llm))
318
+ return undefined;
319
+ const collected = await collectDocuments({
320
+ prompter: input.prompter,
321
+ out: input.out,
322
+ cwd: input.cwd,
323
+ });
324
+ return planFrom({ ...input, documents: collected.documents });
325
+ }
326
+ /**
327
+ * `--config` with a `documents` list. The same analysis runs, but the review
328
+ * cannot: `Prompter.confirm` answers its own default in a non-interactive
329
+ * run, and `runPlanFlow`'s default is "do not review". The plan is therefore
330
+ * saved as a draft, and the site is scaffolded exactly as the rest of the
331
+ * config file describes.
332
+ */
333
+ async function runDocumentStepFromConfig(input) {
334
+ const paths = input.answers.documentPaths ?? [];
335
+ if (paths.length === 0)
336
+ return undefined;
337
+ if (!canPlan(input.llm)) {
338
+ input.out.warn('Documents were listed but no working LLM provider is configured, so they were not read. The site was created from the rest of the config file.');
339
+ return undefined;
340
+ }
341
+ const read = await readDocuments(paths, input.cwd);
342
+ for (const failure of read.failures)
343
+ input.out.bad(failure);
344
+ if (read.documents.length === 0)
345
+ return undefined;
346
+ return planFrom({
347
+ documents: read.documents,
348
+ llm: input.llm,
349
+ out: input.out,
350
+ ...(input.fetchImpl === undefined ? {} : { fetchImpl: input.fetchImpl }),
351
+ // A prompter that always declines: this path must never claim consent.
352
+ prompter: {
353
+ async text(_question, defaultValue) {
354
+ return defaultValue;
355
+ },
356
+ async choice(_question, choices, defaultIndex) {
357
+ return (choices[defaultIndex] ?? choices[0])?.value;
358
+ },
359
+ async confirm(_question, defaultValue) {
360
+ return defaultValue;
361
+ },
362
+ close() { },
363
+ },
364
+ });
365
+ }
157
366
  //# sourceMappingURL=wizard.js.map