@gitpagedocs/cli 1.1.61 → 1.1.63

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 (72) hide show
  1. package/README.md +193 -191
  2. package/ai/application/ai-command.ts +24 -24
  3. package/ai/application/ai-provider-factory.ts +22 -22
  4. package/ai/application/docs-pattern.ts +68 -68
  5. package/ai/application/run-ai-cli-command.ts +167 -167
  6. package/ai/config.ts +50 -50
  7. package/ai/core/models/ai-cli-config.ts +33 -33
  8. package/ai/core/models/parse-ai-cli-config.ts +78 -78
  9. package/ai/core/ports/illm-provider.ts +32 -32
  10. package/ai/infrastructure/ai-config-file.ts +101 -101
  11. package/ai/infrastructure/file-system-adapter.ts +85 -85
  12. package/ai/infrastructure/llm/tools-llm-provider.ts +54 -54
  13. package/ai/infrastructure/version-docs-writer.ts +143 -143
  14. package/ai/presentation/ai-prompts.ts +239 -239
  15. package/application/config-only/handler.mjs +2 -0
  16. package/application/home/handler.mjs +2 -0
  17. package/application/ports/cli-runtime-ports.ts +91 -87
  18. package/application/report/config-only-reporter.mjs +64 -63
  19. package/application/use-cases/dispatch-mode.ts +18 -18
  20. package/builders/config-orchestrator.mjs +34 -34
  21. package/builders/project-links.mjs +50 -26
  22. package/builders/root-config-builder.d.mts +16 -0
  23. package/builders/root-config-builder.mjs +61 -53
  24. package/builders/route-builders.mjs +136 -136
  25. package/builders/theme-template.mjs +76 -76
  26. package/builders/version-config-builder.mjs +186 -186
  27. package/content/docs-en.mjs +549 -545
  28. package/content/docs-es.mjs +462 -460
  29. package/content/docs-pt.mjs +462 -460
  30. package/contracts/doc-versions.mjs +3 -3
  31. package/contracts/index.mjs +10 -0
  32. package/contracts/layouts-paths.d.mts +17 -0
  33. package/contracts/layouts-paths.mjs +68 -0
  34. package/data/default-site-config.mjs +321 -321
  35. package/data/i18n-langmenu.mjs +347 -347
  36. package/data/layouts.mjs +436 -436
  37. package/data/path-mappings.mjs +26 -26
  38. package/data/route-metas.mjs +93 -93
  39. package/data/theme-colors.mjs +443 -443
  40. package/data/urls.mjs +12 -12
  41. package/domain/models/cli-options.ts +33 -18
  42. package/domain/services/layouts-migration.d.mts +15 -0
  43. package/domain/services/layouts-migration.mjs +60 -0
  44. package/home/constants.mjs +4 -4
  45. package/index.mjs +37 -37
  46. package/infrastructure/gitpagedocs-config-file.ts +47 -47
  47. package/infrastructure/home/runtime.mjs +37 -37
  48. package/package.json +3 -3
  49. package/presentation/commands/ai-info.ts +57 -57
  50. package/presentation/commands/config-clear.ts +23 -23
  51. package/presentation/commands/config-info.ts +24 -24
  52. package/presentation/commands/diagnostics.ts +164 -164
  53. package/presentation/commands/docs.ts +37 -37
  54. package/presentation/commands/mcp.ts +20 -20
  55. package/presentation/commands/pages.ts +101 -101
  56. package/presentation/commands/password.ts +62 -62
  57. package/presentation/commands/run-command.ts +70 -70
  58. package/presentation/index.ts +124 -116
  59. package/presentation/options/parser.ts +119 -104
  60. package/presentation/options/resolver.ts +46 -48
  61. package/presentation/options/schema.ts +4 -0
  62. package/presentation/ui/clack.ts +91 -91
  63. package/presentation/ui/layouts-prompts.ts +124 -0
  64. package/presentation/ui/prompts.ts +146 -138
  65. package/presentation/ui/tty.ts +37 -0
  66. package/runtime/git-ops.mjs +110 -110
  67. package/runtime/layouts-migrator.d.mts +11 -0
  68. package/runtime/layouts-migrator.mjs +103 -0
  69. package/runtime/output.mjs +159 -146
  70. package/runtime/workflow.mjs +129 -129
  71. package/start.mjs +12 -12
  72. package/ui/logger.mjs +9 -9
@@ -1,167 +1,167 @@
1
- import { AiCommandService } from "./ai-command";
2
- import { createAiProvider } from "./ai-provider-factory";
3
- import { FileSystemAdapter, type FilePayload } from "../infrastructure/file-system-adapter";
4
- import {
5
- AiConfigFileRepository,
6
- } from "../infrastructure/ai-config-file";
7
- import type { AiCliRunPlan, AiCliRunSummary } from "../core/models/ai-cli-config";
8
- import { promptMissingDirectories, runAiInteractivePrompt } from "../presentation/ai-prompts";
9
- import { GITPAGEDOCS_DOC_SYSTEM_PROMPT } from "../config";
10
- import { parseAiPages, type AiDocPage } from "./docs-pattern";
11
- import { writeVersionDocs } from "../infrastructure/version-docs-writer";
12
- // @ts-expect-error .mjs runtime module is type-less in this package.
13
- import { DOC_VERSIONS } from "../../contracts/doc-versions.mjs";
14
-
15
- const LANGUAGE_TO_LABEL: Record<string, string> = {
16
- pt: "Portuguese",
17
- en: "English",
18
- es: "Español",
19
- };
20
-
21
- function aggregateFilesPayload(files: FilePayload[]): string {
22
- return files
23
- .map((file) => `\n\n--- FILE: ${file.filePath} ---\n\n${file.content}`)
24
- .join("");
25
- }
26
-
27
- function buildLanguagePrompt(basePrompt: string, language: "pt" | "en" | "es"): string {
28
- const languageLabel = LANGUAGE_TO_LABEL[language] ?? language;
29
- const authorGuidance = basePrompt?.trim() ? `Additional author guidance:\n${basePrompt.trim()}\n\n` : "";
30
- return (
31
- `${GITPAGEDOCS_DOC_SYSTEM_PROMPT}\n\n` +
32
- authorGuidance +
33
- `Output language: write every page Title and body 100% in ${languageLabel}. ` +
34
- `Keep each page slug lowercase-kebab and in English so the languages line up.`
35
- );
36
- }
37
-
38
- async function collectFilesWithInteractiveFallback(
39
- fileSystem: FileSystemAdapter,
40
- initialPaths: string[],
41
- ): Promise<{ files: FilePayload[]; scanned: string[]; skipped: string[] }> {
42
- let queue = [...initialPaths];
43
- let scanned: string[] = [];
44
- let skipped: string[] = [];
45
- const files: FilePayload[] = [];
46
-
47
- while (queue.length > 0) {
48
- const { existing, missing } = fileSystem.splitExistingDirectories(queue);
49
- scanned = [...scanned, ...existing];
50
-
51
- for (const directory of existing) {
52
- const directoryFiles = await fileSystem.readDirectoryRecursively(directory);
53
- files.push(...directoryFiles);
54
- }
55
-
56
- if (!missing.length) break;
57
-
58
- const fallback = await promptMissingDirectories(missing);
59
- if (fallback.abort) {
60
- skipped = [...skipped, ...missing];
61
- break;
62
- }
63
-
64
- if (!fallback.replacementPaths.length) {
65
- skipped = [...skipped, ...missing];
66
- break;
67
- }
68
-
69
- queue = fallback.replacementPaths;
70
- }
71
-
72
- return {
73
- files,
74
- scanned,
75
- skipped,
76
- };
77
- }
78
-
79
- /** Factory seam so tests can inject a deterministic provider (DIP). */
80
- export type AiProviderFactory = typeof createAiProvider;
81
-
82
- export async function executeAiRunPlan(
83
- plan: AiCliRunPlan,
84
- cwd: string,
85
- createProvider: AiProviderFactory = createAiProvider,
86
- ): Promise<AiCliRunSummary> {
87
- const provider = createProvider({
88
- provider: plan.config.ai.provider,
89
- model: plan.config.ai.model,
90
- apiKey: plan.config.ai.apiKey,
91
- baseUrl: plan.config.ai.baseUrl,
92
- });
93
-
94
- const aiService = new AiCommandService(provider);
95
- const fileSystem = new FileSystemAdapter(cwd);
96
-
97
- const { files, scanned, skipped } = await collectFilesWithInteractiveFallback(
98
- fileSystem,
99
- plan.config.ai.paths,
100
- );
101
-
102
- if (!files.length) {
103
- return {
104
- scannedDirectories: scanned,
105
- skippedDirectories: skipped,
106
- scannedFilesCount: 0,
107
- outputs: [],
108
- };
109
- }
110
-
111
- const payload = aggregateFilesPayload(files);
112
- const pagesByLang: Partial<Record<"pt" | "en" | "es", AiDocPage[]>> = {};
113
-
114
- for (const language of plan.config.ai.languages) {
115
- const prompt = buildLanguagePrompt(plan.config.ai.contextPrompt, language);
116
- const markdown = await aiService.runGeneration(payload, prompt);
117
- pagesByLang[language] = parseAiPages(markdown);
118
- }
119
-
120
- // Wire the AI pages into the latest documentation version (gitpagedocs pattern).
121
- const versionId = DOC_VERSIONS[DOC_VERSIONS.length - 1] as string;
122
- const result = await writeVersionDocs({ cwd, versionId, pagesByLang });
123
-
124
- return {
125
- scannedDirectories: scanned,
126
- skippedDirectories: skipped,
127
- scannedFilesCount: files.length,
128
- outputs: result.files,
129
- pages: result.slugs,
130
- };
131
- }
132
-
133
- export async function runAiCliCommand(options: {
134
- cwd: string;
135
- onInfo?: (message: string) => void;
136
- /** Provided by the composition root to scaffold the base gitpagedocs/ tree
137
- * (so the version config.json exists before AI pages are wired into it). */
138
- onScaffold?: () => Promise<void>;
139
- }): Promise<{ summary: AiCliRunSummary; runConfigScaffold: boolean }> {
140
- const logInfo = options.onInfo ?? (() => undefined);
141
- const configRepo = new AiConfigFileRepository({
142
- cwd: options.cwd,
143
- onMigrate: (fromPath, toPath) =>
144
- logInfo(`[gitpagedocs:ai] Moved configuration from ${fromPath} to ${toPath} (secure user config directory).`),
145
- });
146
-
147
- const existingConfig = await configRepo.read();
148
- const plan = await runAiInteractivePrompt(existingConfig);
149
-
150
- if (plan.saveConfig) {
151
- await configRepo.write(plan.config);
152
- logInfo(`[gitpagedocs:ai] Configuration saved to ${configRepo.getConfigPath()}`);
153
- }
154
-
155
- // Build the base gitpagedocs structure BEFORE generation so the version
156
- // config.json exists and the AI pages can be wired into it.
157
- if (plan.runConfigScaffold && options.onScaffold) {
158
- logInfo("[gitpagedocs] Generating base gitpagedocs structure...");
159
- await options.onScaffold();
160
- }
161
-
162
- const summary = await executeAiRunPlan(plan, options.cwd);
163
- return {
164
- summary,
165
- runConfigScaffold: plan.runConfigScaffold,
166
- };
167
- }
1
+ import { AiCommandService } from "./ai-command";
2
+ import { createAiProvider } from "./ai-provider-factory";
3
+ import { FileSystemAdapter, type FilePayload } from "../infrastructure/file-system-adapter";
4
+ import {
5
+ AiConfigFileRepository,
6
+ } from "../infrastructure/ai-config-file";
7
+ import type { AiCliRunPlan, AiCliRunSummary } from "../core/models/ai-cli-config";
8
+ import { promptMissingDirectories, runAiInteractivePrompt } from "../presentation/ai-prompts";
9
+ import { GITPAGEDOCS_DOC_SYSTEM_PROMPT } from "../config";
10
+ import { parseAiPages, type AiDocPage } from "./docs-pattern";
11
+ import { writeVersionDocs } from "../infrastructure/version-docs-writer";
12
+ // @ts-expect-error .mjs runtime module is type-less in this package.
13
+ import { DOC_VERSIONS } from "../../contracts/doc-versions.mjs";
14
+
15
+ const LANGUAGE_TO_LABEL: Record<string, string> = {
16
+ pt: "Portuguese",
17
+ en: "English",
18
+ es: "Español",
19
+ };
20
+
21
+ function aggregateFilesPayload(files: FilePayload[]): string {
22
+ return files
23
+ .map((file) => `\n\n--- FILE: ${file.filePath} ---\n\n${file.content}`)
24
+ .join("");
25
+ }
26
+
27
+ function buildLanguagePrompt(basePrompt: string, language: "pt" | "en" | "es"): string {
28
+ const languageLabel = LANGUAGE_TO_LABEL[language] ?? language;
29
+ const authorGuidance = basePrompt?.trim() ? `Additional author guidance:\n${basePrompt.trim()}\n\n` : "";
30
+ return (
31
+ `${GITPAGEDOCS_DOC_SYSTEM_PROMPT}\n\n` +
32
+ authorGuidance +
33
+ `Output language: write every page Title and body 100% in ${languageLabel}. ` +
34
+ `Keep each page slug lowercase-kebab and in English so the languages line up.`
35
+ );
36
+ }
37
+
38
+ async function collectFilesWithInteractiveFallback(
39
+ fileSystem: FileSystemAdapter,
40
+ initialPaths: string[],
41
+ ): Promise<{ files: FilePayload[]; scanned: string[]; skipped: string[] }> {
42
+ let queue = [...initialPaths];
43
+ let scanned: string[] = [];
44
+ let skipped: string[] = [];
45
+ const files: FilePayload[] = [];
46
+
47
+ while (queue.length > 0) {
48
+ const { existing, missing } = fileSystem.splitExistingDirectories(queue);
49
+ scanned = [...scanned, ...existing];
50
+
51
+ for (const directory of existing) {
52
+ const directoryFiles = await fileSystem.readDirectoryRecursively(directory);
53
+ files.push(...directoryFiles);
54
+ }
55
+
56
+ if (!missing.length) break;
57
+
58
+ const fallback = await promptMissingDirectories(missing);
59
+ if (fallback.abort) {
60
+ skipped = [...skipped, ...missing];
61
+ break;
62
+ }
63
+
64
+ if (!fallback.replacementPaths.length) {
65
+ skipped = [...skipped, ...missing];
66
+ break;
67
+ }
68
+
69
+ queue = fallback.replacementPaths;
70
+ }
71
+
72
+ return {
73
+ files,
74
+ scanned,
75
+ skipped,
76
+ };
77
+ }
78
+
79
+ /** Factory seam so tests can inject a deterministic provider (DIP). */
80
+ export type AiProviderFactory = typeof createAiProvider;
81
+
82
+ export async function executeAiRunPlan(
83
+ plan: AiCliRunPlan,
84
+ cwd: string,
85
+ createProvider: AiProviderFactory = createAiProvider,
86
+ ): Promise<AiCliRunSummary> {
87
+ const provider = createProvider({
88
+ provider: plan.config.ai.provider,
89
+ model: plan.config.ai.model,
90
+ apiKey: plan.config.ai.apiKey,
91
+ baseUrl: plan.config.ai.baseUrl,
92
+ });
93
+
94
+ const aiService = new AiCommandService(provider);
95
+ const fileSystem = new FileSystemAdapter(cwd);
96
+
97
+ const { files, scanned, skipped } = await collectFilesWithInteractiveFallback(
98
+ fileSystem,
99
+ plan.config.ai.paths,
100
+ );
101
+
102
+ if (!files.length) {
103
+ return {
104
+ scannedDirectories: scanned,
105
+ skippedDirectories: skipped,
106
+ scannedFilesCount: 0,
107
+ outputs: [],
108
+ };
109
+ }
110
+
111
+ const payload = aggregateFilesPayload(files);
112
+ const pagesByLang: Partial<Record<"pt" | "en" | "es", AiDocPage[]>> = {};
113
+
114
+ for (const language of plan.config.ai.languages) {
115
+ const prompt = buildLanguagePrompt(plan.config.ai.contextPrompt, language);
116
+ const markdown = await aiService.runGeneration(payload, prompt);
117
+ pagesByLang[language] = parseAiPages(markdown);
118
+ }
119
+
120
+ // Wire the AI pages into the latest documentation version (gitpagedocs pattern).
121
+ const versionId = DOC_VERSIONS[DOC_VERSIONS.length - 1] as string;
122
+ const result = await writeVersionDocs({ cwd, versionId, pagesByLang });
123
+
124
+ return {
125
+ scannedDirectories: scanned,
126
+ skippedDirectories: skipped,
127
+ scannedFilesCount: files.length,
128
+ outputs: result.files,
129
+ pages: result.slugs,
130
+ };
131
+ }
132
+
133
+ export async function runAiCliCommand(options: {
134
+ cwd: string;
135
+ onInfo?: (message: string) => void;
136
+ /** Provided by the composition root to scaffold the base gitpagedocs/ tree
137
+ * (so the version config.json exists before AI pages are wired into it). */
138
+ onScaffold?: () => Promise<void>;
139
+ }): Promise<{ summary: AiCliRunSummary; runConfigScaffold: boolean }> {
140
+ const logInfo = options.onInfo ?? (() => undefined);
141
+ const configRepo = new AiConfigFileRepository({
142
+ cwd: options.cwd,
143
+ onMigrate: (fromPath, toPath) =>
144
+ logInfo(`[gitpagedocs:ai] Moved configuration from ${fromPath} to ${toPath} (secure user config directory).`),
145
+ });
146
+
147
+ const existingConfig = await configRepo.read();
148
+ const plan = await runAiInteractivePrompt(existingConfig);
149
+
150
+ if (plan.saveConfig) {
151
+ await configRepo.write(plan.config);
152
+ logInfo(`[gitpagedocs:ai] Configuration saved to ${configRepo.getConfigPath()}`);
153
+ }
154
+
155
+ // Build the base gitpagedocs structure BEFORE generation so the version
156
+ // config.json exists and the AI pages can be wired into it.
157
+ if (plan.runConfigScaffold && options.onScaffold) {
158
+ logInfo("[gitpagedocs] Generating base gitpagedocs structure...");
159
+ await options.onScaffold();
160
+ }
161
+
162
+ const summary = await executeAiRunPlan(plan, options.cwd);
163
+ return {
164
+ summary,
165
+ runConfigScaffold: plan.runConfigScaffold,
166
+ };
167
+ }
package/ai/config.ts CHANGED
@@ -1,50 +1,50 @@
1
- /**
2
- * @file config.ts
3
- * @description Provider constants for the AI CLI. Co-located in cli/ so the
4
- * published `gitpagedocs` package does not reach into the frontend app. The
5
- * browser keeps its own copy at frontend/src/shared/config/ai-config.ts.
6
- */
7
-
8
- /** Legacy 4-provider id used by the interactive CLI (mapped to the 14-provider
9
- * catalog in @gitpagedocs/tools via legacyProviderToCatalogId). */
10
- export type AiProviderId = 'openai' | 'claude' | 'gemini' | 'ollama';
11
-
12
- export const AI_MODEL_DEFAULTS: Readonly<Record<AiProviderId, string>> = {
13
- openai: 'gpt-4o-mini',
14
- claude: 'claude-3-5-sonnet-20240620',
15
- gemini: 'gemini-1.5-flash',
16
- ollama: 'llama3',
17
- };
18
-
19
- export const DEFAULT_AI_DOC_PROMPT =
20
- 'You are a senior tech writer. Describe the provided code.';
21
-
22
- /**
23
- * System prompt that teaches the model what gitpagedocs is and forces it to
24
- * return documentation in the gitpagedocs pattern: a sequence of pages, each
25
- * introduced by a machine-parseable delimiter line so the CLI can split the
26
- * response into individual versioned markdown pages and wire them into the
27
- * viewer's config.json (routes-md + menus-header-md).
28
- */
29
- export const GITPAGEDOCS_DOC_SYSTEM_PROMPT = [
30
- 'You are the documentation engine for "gitpagedocs", a generator of multilingual,',
31
- 'versioned static documentation sites. gitpagedocs renders markdown pages from',
32
- 'gitpagedocs/docs/versions/<version>/<lang>/<page>.md and wires them into a viewer',
33
- 'through a config.json contract (routes + header menus). Your job is to analyze the',
34
- 'provided source code and produce clean, professional documentation that fits this',
35
- 'pattern so it can be dropped straight into a gitpagedocs site.',
36
- '',
37
- 'STRICT OUTPUT FORMAT — return ONLY a sequence of pages. Begin every page with a',
38
- 'delimiter line on its own, exactly:',
39
- '=== PAGE: <slug> | <Title> ===',
40
- 'followed by the page body in GitHub-flavored markdown. Rules:',
41
- '- Produce 4 to 8 cohesive pages. Suggested slugs: overview, getting-started,',
42
- ' architecture, configuration, usage, deployment (adapt to the project).',
43
- '- <slug> MUST be lowercase-kebab-case and in ENGLISH, and identical across every',
44
- ' language run, so the languages line up (only the Title and body are translated).',
45
- '- Do NOT wrap the whole answer in a code fence. Do NOT add any preamble or epilogue',
46
- ' outside the page blocks. The first line of your reply MUST be a "=== PAGE:" line.',
47
- '- Base everything strictly on the analyzed files; do not invent features.',
48
- ].join('\n');
49
-
50
- export const OLLAMA_DEFAULT_BASE_URL = 'http://localhost:11434';
1
+ /**
2
+ * @file config.ts
3
+ * @description Provider constants for the AI CLI. Co-located in cli/ so the
4
+ * published `gitpagedocs` package does not reach into the frontend app. The
5
+ * browser keeps its own copy at frontend/src/shared/config/ai-config.ts.
6
+ */
7
+
8
+ /** Legacy 4-provider id used by the interactive CLI (mapped to the 14-provider
9
+ * catalog in @gitpagedocs/tools via legacyProviderToCatalogId). */
10
+ export type AiProviderId = 'openai' | 'claude' | 'gemini' | 'ollama';
11
+
12
+ export const AI_MODEL_DEFAULTS: Readonly<Record<AiProviderId, string>> = {
13
+ openai: 'gpt-4o-mini',
14
+ claude: 'claude-3-5-sonnet-20240620',
15
+ gemini: 'gemini-1.5-flash',
16
+ ollama: 'llama3',
17
+ };
18
+
19
+ export const DEFAULT_AI_DOC_PROMPT =
20
+ 'You are a senior tech writer. Describe the provided code.';
21
+
22
+ /**
23
+ * System prompt that teaches the model what gitpagedocs is and forces it to
24
+ * return documentation in the gitpagedocs pattern: a sequence of pages, each
25
+ * introduced by a machine-parseable delimiter line so the CLI can split the
26
+ * response into individual versioned markdown pages and wire them into the
27
+ * viewer's config.json (routes-md + menus-header-md).
28
+ */
29
+ export const GITPAGEDOCS_DOC_SYSTEM_PROMPT = [
30
+ 'You are the documentation engine for "gitpagedocs", a generator of multilingual,',
31
+ 'versioned static documentation sites. gitpagedocs renders markdown pages from',
32
+ 'gitpagedocs/docs/versions/<version>/<lang>/<page>.md and wires them into a viewer',
33
+ 'through a config.json contract (routes + header menus). Your job is to analyze the',
34
+ 'provided source code and produce clean, professional documentation that fits this',
35
+ 'pattern so it can be dropped straight into a gitpagedocs site.',
36
+ '',
37
+ 'STRICT OUTPUT FORMAT — return ONLY a sequence of pages. Begin every page with a',
38
+ 'delimiter line on its own, exactly:',
39
+ '=== PAGE: <slug> | <Title> ===',
40
+ 'followed by the page body in GitHub-flavored markdown. Rules:',
41
+ '- Produce 4 to 8 cohesive pages. Suggested slugs: overview, getting-started,',
42
+ ' architecture, configuration, usage, deployment (adapt to the project).',
43
+ '- <slug> MUST be lowercase-kebab-case and in ENGLISH, and identical across every',
44
+ ' language run, so the languages line up (only the Title and body are translated).',
45
+ '- Do NOT wrap the whole answer in a code fence. Do NOT add any preamble or epilogue',
46
+ ' outside the page blocks. The first line of your reply MUST be a "=== PAGE:" line.',
47
+ '- Base everything strictly on the analyzed files; do not invent features.',
48
+ ].join('\n');
49
+
50
+ export const OLLAMA_DEFAULT_BASE_URL = 'http://localhost:11434';
@@ -1,33 +1,33 @@
1
- import type { AiProviderId } from "../../config";
2
-
3
- export const AI_CLI_CONFIG_FILENAME = ".gitpagedocsconfig";
4
-
5
- export interface AiCliConfig {
6
- version: 1;
7
- ai: {
8
- provider: AiProviderId;
9
- model: string;
10
- apiKey?: string;
11
- baseUrl?: string;
12
- paths: string[];
13
- languages: Array<"pt" | "en" | "es">;
14
- outputDir: string;
15
- filePrefix: string;
16
- contextPrompt: string;
17
- };
18
- }
19
-
20
- export interface AiCliRunPlan {
21
- config: AiCliConfig;
22
- saveConfig: boolean;
23
- runConfigScaffold: boolean;
24
- }
25
-
26
- export interface AiCliRunSummary {
27
- scannedDirectories: string[];
28
- skippedDirectories: string[];
29
- scannedFilesCount: number;
30
- outputs: string[];
31
- /** Slugs of the gitpagedocs pages wired into the version config (if any). */
32
- pages?: string[];
33
- }
1
+ import type { AiProviderId } from "../../config";
2
+
3
+ export const AI_CLI_CONFIG_FILENAME = ".gitpagedocsconfig";
4
+
5
+ export interface AiCliConfig {
6
+ version: 1;
7
+ ai: {
8
+ provider: AiProviderId;
9
+ model: string;
10
+ apiKey?: string;
11
+ baseUrl?: string;
12
+ paths: string[];
13
+ languages: Array<"pt" | "en" | "es">;
14
+ outputDir: string;
15
+ filePrefix: string;
16
+ contextPrompt: string;
17
+ };
18
+ }
19
+
20
+ export interface AiCliRunPlan {
21
+ config: AiCliConfig;
22
+ saveConfig: boolean;
23
+ runConfigScaffold: boolean;
24
+ }
25
+
26
+ export interface AiCliRunSummary {
27
+ scannedDirectories: string[];
28
+ skippedDirectories: string[];
29
+ scannedFilesCount: number;
30
+ outputs: string[];
31
+ /** Slugs of the gitpagedocs pages wired into the version config (if any). */
32
+ pages?: string[];
33
+ }
@@ -1,78 +1,78 @@
1
- import { AI_MODEL_DEFAULTS, type AiProviderId } from "../../config";
2
- import type { AiCliConfig } from "./ai-cli-config";
3
-
4
- const SUPPORTED_PROVIDERS = Object.keys(AI_MODEL_DEFAULTS) as AiProviderId[];
5
- const SUPPORTED_LANGUAGES: ReadonlyArray<AiCliConfig["ai"]["languages"][number]> = ["pt", "en", "es"];
6
-
7
- /** Fallbacks for optional fields, mirroring the interactive prompt defaults. */
8
- const DEFAULT_LANGUAGES: AiCliConfig["ai"]["languages"] = ["en", "pt", "es"];
9
- const DEFAULT_OUTPUT_DIR = "gitpagedocs/docs";
10
- const DEFAULT_FILE_PREFIX = "ai-generated";
11
-
12
- function isString(value: unknown): value is string {
13
- return typeof value === "string";
14
- }
15
-
16
- function asOptionalString(value: unknown): string | undefined {
17
- return isString(value) ? value : undefined;
18
- }
19
-
20
- function asStringWithDefault(value: unknown, fallback: string): string {
21
- return isString(value) ? value : fallback;
22
- }
23
-
24
- function asPaths(value: unknown): string[] {
25
- return Array.isArray(value) ? value.filter(isString) : [];
26
- }
27
-
28
- function asLanguages(value: unknown): AiCliConfig["ai"]["languages"] {
29
- const languages = Array.isArray(value)
30
- ? value.filter((language): language is AiCliConfig["ai"]["languages"][number] =>
31
- SUPPORTED_LANGUAGES.includes(language as AiCliConfig["ai"]["languages"][number]),
32
- )
33
- : [];
34
- return languages.length > 0 ? languages : [...DEFAULT_LANGUAGES];
35
- }
36
-
37
- function asProviderId(value: unknown): AiProviderId | null {
38
- return isString(value) && SUPPORTED_PROVIDERS.includes(value as AiProviderId)
39
- ? (value as AiProviderId)
40
- : null;
41
- }
42
-
43
- /**
44
- * Validate an untrusted `.gitpagedocsconfig` payload. Tolerant by design so
45
- * hand-edited configs keep working: only the version-1 envelope and a known
46
- * provider are required; every other field falls back to the interactive
47
- * prompt defaults. Returns a sanitized {@link AiCliConfig} with only known
48
- * fields, or `null` when the payload cannot identify a provider.
49
- */
50
- export function parseAiCliConfig(raw: unknown): AiCliConfig | null {
51
- if (typeof raw !== "object" || raw === null) return null;
52
- const candidate = raw as Partial<AiCliConfig>;
53
- if (candidate.version !== 1) return null;
54
-
55
- const ai = candidate.ai;
56
- if (typeof ai !== "object" || ai === null) return null;
57
- const provider = asProviderId(ai.provider);
58
- if (!provider) return null;
59
-
60
- const model = asOptionalString(ai.model)?.trim() || AI_MODEL_DEFAULTS[provider];
61
- const apiKey = asOptionalString(ai.apiKey);
62
- const baseUrl = asOptionalString(ai.baseUrl);
63
-
64
- return {
65
- version: 1,
66
- ai: {
67
- provider,
68
- model,
69
- ...(apiKey !== undefined ? { apiKey } : {}),
70
- ...(baseUrl !== undefined ? { baseUrl } : {}),
71
- paths: asPaths(ai.paths),
72
- languages: asLanguages(ai.languages),
73
- outputDir: asStringWithDefault(ai.outputDir, DEFAULT_OUTPUT_DIR),
74
- filePrefix: asStringWithDefault(ai.filePrefix, DEFAULT_FILE_PREFIX),
75
- contextPrompt: asStringWithDefault(ai.contextPrompt, ""),
76
- },
77
- };
78
- }
1
+ import { AI_MODEL_DEFAULTS, type AiProviderId } from "../../config";
2
+ import type { AiCliConfig } from "./ai-cli-config";
3
+
4
+ const SUPPORTED_PROVIDERS = Object.keys(AI_MODEL_DEFAULTS) as AiProviderId[];
5
+ const SUPPORTED_LANGUAGES: ReadonlyArray<AiCliConfig["ai"]["languages"][number]> = ["pt", "en", "es"];
6
+
7
+ /** Fallbacks for optional fields, mirroring the interactive prompt defaults. */
8
+ const DEFAULT_LANGUAGES: AiCliConfig["ai"]["languages"] = ["en", "pt", "es"];
9
+ const DEFAULT_OUTPUT_DIR = "gitpagedocs/docs";
10
+ const DEFAULT_FILE_PREFIX = "ai-generated";
11
+
12
+ function isString(value: unknown): value is string {
13
+ return typeof value === "string";
14
+ }
15
+
16
+ function asOptionalString(value: unknown): string | undefined {
17
+ return isString(value) ? value : undefined;
18
+ }
19
+
20
+ function asStringWithDefault(value: unknown, fallback: string): string {
21
+ return isString(value) ? value : fallback;
22
+ }
23
+
24
+ function asPaths(value: unknown): string[] {
25
+ return Array.isArray(value) ? value.filter(isString) : [];
26
+ }
27
+
28
+ function asLanguages(value: unknown): AiCliConfig["ai"]["languages"] {
29
+ const languages = Array.isArray(value)
30
+ ? value.filter((language): language is AiCliConfig["ai"]["languages"][number] =>
31
+ SUPPORTED_LANGUAGES.includes(language as AiCliConfig["ai"]["languages"][number]),
32
+ )
33
+ : [];
34
+ return languages.length > 0 ? languages : [...DEFAULT_LANGUAGES];
35
+ }
36
+
37
+ function asProviderId(value: unknown): AiProviderId | null {
38
+ return isString(value) && SUPPORTED_PROVIDERS.includes(value as AiProviderId)
39
+ ? (value as AiProviderId)
40
+ : null;
41
+ }
42
+
43
+ /**
44
+ * Validate an untrusted `.gitpagedocsconfig` payload. Tolerant by design so
45
+ * hand-edited configs keep working: only the version-1 envelope and a known
46
+ * provider are required; every other field falls back to the interactive
47
+ * prompt defaults. Returns a sanitized {@link AiCliConfig} with only known
48
+ * fields, or `null` when the payload cannot identify a provider.
49
+ */
50
+ export function parseAiCliConfig(raw: unknown): AiCliConfig | null {
51
+ if (typeof raw !== "object" || raw === null) return null;
52
+ const candidate = raw as Partial<AiCliConfig>;
53
+ if (candidate.version !== 1) return null;
54
+
55
+ const ai = candidate.ai;
56
+ if (typeof ai !== "object" || ai === null) return null;
57
+ const provider = asProviderId(ai.provider);
58
+ if (!provider) return null;
59
+
60
+ const model = asOptionalString(ai.model)?.trim() || AI_MODEL_DEFAULTS[provider];
61
+ const apiKey = asOptionalString(ai.apiKey);
62
+ const baseUrl = asOptionalString(ai.baseUrl);
63
+
64
+ return {
65
+ version: 1,
66
+ ai: {
67
+ provider,
68
+ model,
69
+ ...(apiKey !== undefined ? { apiKey } : {}),
70
+ ...(baseUrl !== undefined ? { baseUrl } : {}),
71
+ paths: asPaths(ai.paths),
72
+ languages: asLanguages(ai.languages),
73
+ outputDir: asStringWithDefault(ai.outputDir, DEFAULT_OUTPUT_DIR),
74
+ filePrefix: asStringWithDefault(ai.filePrefix, DEFAULT_FILE_PREFIX),
75
+ contextPrompt: asStringWithDefault(ai.contextPrompt, ""),
76
+ },
77
+ };
78
+ }