@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,239 +1,239 @@
1
- import {
2
- AI_MODEL_DEFAULTS,
3
- OLLAMA_DEFAULT_BASE_URL,
4
- type AiProviderId,
5
- } from '../config';
6
- import { PROVIDER_CATALOG, legacyProviderToCatalogId } from '@gitpagedocs/tools/ai';
7
- import type { AiCliConfig, AiCliRunPlan } from '../core/models/ai-cli-config';
8
- import {
9
- askText,
10
- askConfirm,
11
- askSelect,
12
- askMultiSelect,
13
- intro,
14
- } from '../../presentation/ui/clack';
15
-
16
- export interface InteractiveCliAnswers {
17
- provider: AiProviderId;
18
- apiKeyOrHost: string;
19
- model: string;
20
- targetDirs: string[];
21
- languages: Array<'pt' | 'en' | 'es'>;
22
- outputDir: string;
23
- filePrefix: string;
24
- saveConfig: boolean;
25
- runConfigScaffold: boolean;
26
- contextPrompt: string;
27
- }
28
-
29
- function parseCommaSeparated(value: string): string[] {
30
- return value
31
- .split(',')
32
- .map((item) => item.trim())
33
- .filter(Boolean);
34
- }
35
-
36
- function validateCommaSeparatedDirectories(value: string): string | undefined {
37
- const values = parseCommaSeparated(value);
38
- if (!values.length) return 'Provide at least one path.';
39
- return undefined;
40
- }
41
-
42
- const CUSTOM_MODEL = '__custom_model__';
43
-
44
- /**
45
- * Ask for the model as a SELECT of the provider's real catalog models (with the
46
- * default marked), plus a "Custom" escape hatch — so an invalid id like "4.8"
47
- * can't be typed by mistake and rejected later as a 404 by the provider.
48
- */
49
- async function askModel(provider: AiProviderId): Promise<string> {
50
- const catalogId = legacyProviderToCatalogId(provider);
51
- const spec = catalogId ? PROVIDER_CATALOG[catalogId] : undefined;
52
- const fallback = AI_MODEL_DEFAULTS[provider] || '';
53
-
54
- if (!spec || spec.models.length === 0) {
55
- return String(await askText({ message: 'Which model do you want to use?', defaultValue: fallback }));
56
- }
57
-
58
- const picked = await askSelect<string>(
59
- 'Which model do you want to use?',
60
- [
61
- ...spec.models.map((m) => ({
62
- value: m.id,
63
- label: m.id === spec.defaultModel ? `${m.id} (default)` : m.id,
64
- })),
65
- { value: CUSTOM_MODEL, label: 'Custom — enter a model id manually' },
66
- ],
67
- spec.defaultModel,
68
- );
69
-
70
- if (picked !== CUSTOM_MODEL) return picked;
71
-
72
- return String(
73
- await askText({
74
- message: 'Enter the model id:',
75
- defaultValue: spec.defaultModel,
76
- validate: (value) => (value.trim() ? undefined : 'Provide a model id'),
77
- }),
78
- );
79
- }
80
-
81
- function toPlan(answers: InteractiveCliAnswers): AiCliRunPlan {
82
- const config: AiCliConfig = {
83
- version: 1,
84
- ai: {
85
- provider: answers.provider,
86
- model: answers.model,
87
- apiKey: answers.provider === 'ollama' ? undefined : answers.apiKeyOrHost,
88
- baseUrl: answers.provider === 'ollama' ? answers.apiKeyOrHost : undefined,
89
- paths: answers.targetDirs,
90
- languages: answers.languages,
91
- outputDir: answers.outputDir,
92
- filePrefix: answers.filePrefix,
93
- contextPrompt: answers.contextPrompt,
94
- },
95
- };
96
-
97
- return {
98
- config,
99
- saveConfig: answers.saveConfig,
100
- runConfigScaffold: answers.runConfigScaffold,
101
- };
102
- }
103
-
104
- export async function promptMissingDirectories(
105
- missingDirectories: string[],
106
- ): Promise<{ replacementPaths: string[]; abort: boolean }> {
107
- const recoveryMode = await askSelect(
108
- `I couldn't find ${missingDirectories.length} directory(ies): ${missingDirectories.join(', ')}`,
109
- [
110
- { value: 'fix', label: 'Fix paths now (recommended)' },
111
- { value: 'skip', label: 'Skip missing paths and continue' },
112
- { value: 'abort', label: 'Abort execution' },
113
- ],
114
- 'fix',
115
- );
116
-
117
- if (recoveryMode === 'abort') {
118
- return { replacementPaths: [], abort: true };
119
- }
120
- if (recoveryMode === 'skip') {
121
- return { replacementPaths: [], abort: false };
122
- }
123
-
124
- const fixedPaths = await askText({
125
- message: 'Enter new paths (comma-separated):',
126
- validate: validateCommaSeparatedDirectories,
127
- });
128
-
129
- return {
130
- replacementPaths: parseCommaSeparated(String(fixedPaths || '')),
131
- abort: false,
132
- };
133
- }
134
-
135
- export async function promptUseExistingConfig(existingConfig: AiCliConfig): Promise<AiCliRunPlan | null> {
136
- const useExistingConfig = await askConfirm(
137
- 'Found .gitpagedocsconfig. Do you want to use this configuration?',
138
- true,
139
- );
140
-
141
- if (!useExistingConfig) return null;
142
-
143
- const runConfigScaffold = await askConfirm(
144
- 'Generate the base gitpagedocs structure first, so the AI pages are wired into the docs viewer?',
145
- true,
146
- );
147
-
148
- return {
149
- config: existingConfig,
150
- saveConfig: false,
151
- runConfigScaffold,
152
- };
153
- }
154
-
155
- export async function runAiInteractivePrompt(existingConfig?: AiCliConfig | null): Promise<AiCliRunPlan> {
156
- intro('GitPageDocs AI CLI - interactive mode');
157
-
158
- if (existingConfig) {
159
- const existingPlan = await promptUseExistingConfig(existingConfig);
160
- if (existingPlan) return existingPlan;
161
- }
162
-
163
- const provider = await askSelect<AiProviderId>(
164
- 'Which AI provider should be used to generate documentation?',
165
- [
166
- { value: 'openai', label: 'OpenAI (GPT)' },
167
- { value: 'claude', label: 'Anthropic (Claude)' },
168
- { value: 'gemini', label: 'Google Gemini' },
169
- { value: 'ollama', label: 'Ollama (Local Open Source)' },
170
- ],
171
- 'openai',
172
- );
173
-
174
- const apiKeyOrHost = await askText({
175
- message: provider === 'ollama' ? 'Ollama host URL:' : 'Provider API key:',
176
- defaultValue: provider === 'ollama' ? OLLAMA_DEFAULT_BASE_URL : undefined,
177
- validate: (input) => (input.length > 0 ? undefined : 'Required field'),
178
- });
179
-
180
- const model = await askModel(provider);
181
-
182
- const targetDirsRaw = await askText({
183
- message:
184
- 'Paths to analyze (comma-separated). You can include multiple repositories (e.g., src,cli,../other-repo/src):',
185
- defaultValue: 'src,cli',
186
- validate: validateCommaSeparatedDirectories,
187
- });
188
-
189
- const languages = await askMultiSelect<'en' | 'pt' | 'es'>(
190
- 'Documentation languages to generate:',
191
- [
192
- { value: 'en', label: 'English (en)' },
193
- { value: 'pt', label: 'Portuguese (pt)' },
194
- { value: 'es', label: 'Español (es)' },
195
- ],
196
- ['en', 'pt', 'es'],
197
- );
198
-
199
- const outputDir = await askText({
200
- message: 'Output directory for generated markdown files:',
201
- defaultValue: 'gitpagedocs/docs',
202
- validate: (value) => (value.trim() ? undefined : 'Provide an output directory'),
203
- });
204
-
205
- const filePrefix = await askText({
206
- message: 'Output file prefix:',
207
- defaultValue: 'ai-generated',
208
- validate: (value) => (value.trim() ? undefined : 'Provide a prefix'),
209
- });
210
-
211
- const contextPrompt = await askText({
212
- message: 'Extra instructions for AI (optional):',
213
- defaultValue:
214
- 'You are a senior technical writer. Generate professional, clear documentation with architectural insight following FSD, SOLID, and Clean Code principles.',
215
- });
216
-
217
- const saveConfig = await askConfirm(
218
- 'Save this configuration to .gitpagedocsconfig for future manual use?',
219
- true,
220
- );
221
-
222
- const runConfigScaffold = await askConfirm(
223
- 'Generate the base gitpagedocs structure first, so the AI pages are wired into the docs viewer?',
224
- true,
225
- );
226
-
227
- return toPlan({
228
- provider,
229
- apiKeyOrHost,
230
- model,
231
- targetDirs: parseCommaSeparated(targetDirsRaw),
232
- languages,
233
- outputDir,
234
- filePrefix,
235
- contextPrompt,
236
- saveConfig,
237
- runConfigScaffold,
238
- });
239
- }
1
+ import {
2
+ AI_MODEL_DEFAULTS,
3
+ OLLAMA_DEFAULT_BASE_URL,
4
+ type AiProviderId,
5
+ } from '../config';
6
+ import { PROVIDER_CATALOG, legacyProviderToCatalogId } from '@gitpagedocs/tools/ai';
7
+ import type { AiCliConfig, AiCliRunPlan } from '../core/models/ai-cli-config';
8
+ import {
9
+ askText,
10
+ askConfirm,
11
+ askSelect,
12
+ askMultiSelect,
13
+ intro,
14
+ } from '../../presentation/ui/clack';
15
+
16
+ export interface InteractiveCliAnswers {
17
+ provider: AiProviderId;
18
+ apiKeyOrHost: string;
19
+ model: string;
20
+ targetDirs: string[];
21
+ languages: Array<'pt' | 'en' | 'es'>;
22
+ outputDir: string;
23
+ filePrefix: string;
24
+ saveConfig: boolean;
25
+ runConfigScaffold: boolean;
26
+ contextPrompt: string;
27
+ }
28
+
29
+ function parseCommaSeparated(value: string): string[] {
30
+ return value
31
+ .split(',')
32
+ .map((item) => item.trim())
33
+ .filter(Boolean);
34
+ }
35
+
36
+ function validateCommaSeparatedDirectories(value: string): string | undefined {
37
+ const values = parseCommaSeparated(value);
38
+ if (!values.length) return 'Provide at least one path.';
39
+ return undefined;
40
+ }
41
+
42
+ const CUSTOM_MODEL = '__custom_model__';
43
+
44
+ /**
45
+ * Ask for the model as a SELECT of the provider's real catalog models (with the
46
+ * default marked), plus a "Custom" escape hatch — so an invalid id like "4.8"
47
+ * can't be typed by mistake and rejected later as a 404 by the provider.
48
+ */
49
+ async function askModel(provider: AiProviderId): Promise<string> {
50
+ const catalogId = legacyProviderToCatalogId(provider);
51
+ const spec = catalogId ? PROVIDER_CATALOG[catalogId] : undefined;
52
+ const fallback = AI_MODEL_DEFAULTS[provider] || '';
53
+
54
+ if (!spec || spec.models.length === 0) {
55
+ return String(await askText({ message: 'Which model do you want to use?', defaultValue: fallback }));
56
+ }
57
+
58
+ const picked = await askSelect<string>(
59
+ 'Which model do you want to use?',
60
+ [
61
+ ...spec.models.map((m) => ({
62
+ value: m.id,
63
+ label: m.id === spec.defaultModel ? `${m.id} (default)` : m.id,
64
+ })),
65
+ { value: CUSTOM_MODEL, label: 'Custom — enter a model id manually' },
66
+ ],
67
+ spec.defaultModel,
68
+ );
69
+
70
+ if (picked !== CUSTOM_MODEL) return picked;
71
+
72
+ return String(
73
+ await askText({
74
+ message: 'Enter the model id:',
75
+ defaultValue: spec.defaultModel,
76
+ validate: (value) => (value.trim() ? undefined : 'Provide a model id'),
77
+ }),
78
+ );
79
+ }
80
+
81
+ function toPlan(answers: InteractiveCliAnswers): AiCliRunPlan {
82
+ const config: AiCliConfig = {
83
+ version: 1,
84
+ ai: {
85
+ provider: answers.provider,
86
+ model: answers.model,
87
+ apiKey: answers.provider === 'ollama' ? undefined : answers.apiKeyOrHost,
88
+ baseUrl: answers.provider === 'ollama' ? answers.apiKeyOrHost : undefined,
89
+ paths: answers.targetDirs,
90
+ languages: answers.languages,
91
+ outputDir: answers.outputDir,
92
+ filePrefix: answers.filePrefix,
93
+ contextPrompt: answers.contextPrompt,
94
+ },
95
+ };
96
+
97
+ return {
98
+ config,
99
+ saveConfig: answers.saveConfig,
100
+ runConfigScaffold: answers.runConfigScaffold,
101
+ };
102
+ }
103
+
104
+ export async function promptMissingDirectories(
105
+ missingDirectories: string[],
106
+ ): Promise<{ replacementPaths: string[]; abort: boolean }> {
107
+ const recoveryMode = await askSelect(
108
+ `I couldn't find ${missingDirectories.length} directory(ies): ${missingDirectories.join(', ')}`,
109
+ [
110
+ { value: 'fix', label: 'Fix paths now (recommended)' },
111
+ { value: 'skip', label: 'Skip missing paths and continue' },
112
+ { value: 'abort', label: 'Abort execution' },
113
+ ],
114
+ 'fix',
115
+ );
116
+
117
+ if (recoveryMode === 'abort') {
118
+ return { replacementPaths: [], abort: true };
119
+ }
120
+ if (recoveryMode === 'skip') {
121
+ return { replacementPaths: [], abort: false };
122
+ }
123
+
124
+ const fixedPaths = await askText({
125
+ message: 'Enter new paths (comma-separated):',
126
+ validate: validateCommaSeparatedDirectories,
127
+ });
128
+
129
+ return {
130
+ replacementPaths: parseCommaSeparated(String(fixedPaths || '')),
131
+ abort: false,
132
+ };
133
+ }
134
+
135
+ export async function promptUseExistingConfig(existingConfig: AiCliConfig): Promise<AiCliRunPlan | null> {
136
+ const useExistingConfig = await askConfirm(
137
+ 'Found .gitpagedocsconfig. Do you want to use this configuration?',
138
+ true,
139
+ );
140
+
141
+ if (!useExistingConfig) return null;
142
+
143
+ const runConfigScaffold = await askConfirm(
144
+ 'Generate the base gitpagedocs structure first, so the AI pages are wired into the docs viewer?',
145
+ true,
146
+ );
147
+
148
+ return {
149
+ config: existingConfig,
150
+ saveConfig: false,
151
+ runConfigScaffold,
152
+ };
153
+ }
154
+
155
+ export async function runAiInteractivePrompt(existingConfig?: AiCliConfig | null): Promise<AiCliRunPlan> {
156
+ intro('GitPageDocs AI CLI - interactive mode');
157
+
158
+ if (existingConfig) {
159
+ const existingPlan = await promptUseExistingConfig(existingConfig);
160
+ if (existingPlan) return existingPlan;
161
+ }
162
+
163
+ const provider = await askSelect<AiProviderId>(
164
+ 'Which AI provider should be used to generate documentation?',
165
+ [
166
+ { value: 'openai', label: 'OpenAI (GPT)' },
167
+ { value: 'claude', label: 'Anthropic (Claude)' },
168
+ { value: 'gemini', label: 'Google Gemini' },
169
+ { value: 'ollama', label: 'Ollama (Local Open Source)' },
170
+ ],
171
+ 'openai',
172
+ );
173
+
174
+ const apiKeyOrHost = await askText({
175
+ message: provider === 'ollama' ? 'Ollama host URL:' : 'Provider API key:',
176
+ defaultValue: provider === 'ollama' ? OLLAMA_DEFAULT_BASE_URL : undefined,
177
+ validate: (input) => (input.length > 0 ? undefined : 'Required field'),
178
+ });
179
+
180
+ const model = await askModel(provider);
181
+
182
+ const targetDirsRaw = await askText({
183
+ message:
184
+ 'Paths to analyze (comma-separated). You can include multiple repositories (e.g., src,cli,../other-repo/src):',
185
+ defaultValue: 'src,cli',
186
+ validate: validateCommaSeparatedDirectories,
187
+ });
188
+
189
+ const languages = await askMultiSelect<'en' | 'pt' | 'es'>(
190
+ 'Documentation languages to generate:',
191
+ [
192
+ { value: 'en', label: 'English (en)' },
193
+ { value: 'pt', label: 'Portuguese (pt)' },
194
+ { value: 'es', label: 'Español (es)' },
195
+ ],
196
+ ['en', 'pt', 'es'],
197
+ );
198
+
199
+ const outputDir = await askText({
200
+ message: 'Output directory for generated markdown files:',
201
+ defaultValue: 'gitpagedocs/docs',
202
+ validate: (value) => (value.trim() ? undefined : 'Provide an output directory'),
203
+ });
204
+
205
+ const filePrefix = await askText({
206
+ message: 'Output file prefix:',
207
+ defaultValue: 'ai-generated',
208
+ validate: (value) => (value.trim() ? undefined : 'Provide a prefix'),
209
+ });
210
+
211
+ const contextPrompt = await askText({
212
+ message: 'Extra instructions for AI (optional):',
213
+ defaultValue:
214
+ 'You are a senior technical writer. Generate professional, clear documentation with architectural insight following FSD, SOLID, and Clean Code principles.',
215
+ });
216
+
217
+ const saveConfig = await askConfirm(
218
+ 'Save this configuration to .gitpagedocsconfig for future manual use?',
219
+ true,
220
+ );
221
+
222
+ const runConfigScaffold = await askConfirm(
223
+ 'Generate the base gitpagedocs structure first, so the AI pages are wired into the docs viewer?',
224
+ true,
225
+ );
226
+
227
+ return toPlan({
228
+ provider,
229
+ apiKeyOrHost,
230
+ model,
231
+ targetDirs: parseCommaSeparated(targetDirsRaw),
232
+ languages,
233
+ outputDir,
234
+ filePrefix,
235
+ contextPrompt,
236
+ saveConfig,
237
+ runConfigScaffold,
238
+ });
239
+ }
@@ -24,6 +24,7 @@ export async function executeConfigOnly(params, runtime) {
24
24
 
25
25
  const artifacts = buildConfigArtifacts({
26
26
  useLocalLayoutConfig: options.useLocalLayoutConfig,
27
+ layoutsDir: options.layoutsDir,
27
28
  githubOwner: sanitizeSegment(options.githubOwner),
28
29
  githubRepo: sanitizeSegment(options.githubRepo),
29
30
  root,
@@ -33,6 +34,7 @@ export async function executeConfigOnly(params, runtime) {
33
34
  root,
34
35
  pkgRoot,
35
36
  outputDir: options.outputDir,
37
+ layoutsDir: options.layoutsDir,
36
38
  artifacts,
37
39
  useLocalLayoutConfig: options.useLocalLayoutConfig,
38
40
  layouts,
@@ -41,6 +41,7 @@ export async function executeHome(params, runtime) {
41
41
 
42
42
  const artifacts = buildConfigArtifacts({
43
43
  useLocalLayoutConfig: false,
44
+ layoutsDir: options.layoutsDir,
44
45
  githubOwner: "",
45
46
  githubRepo: "",
46
47
  root,
@@ -50,6 +51,7 @@ export async function executeHome(params, runtime) {
50
51
  root,
51
52
  pkgRoot,
52
53
  outputDir: ARTIFACTS_DIR,
54
+ layoutsDir: options.layoutsDir,
53
55
  artifacts,
54
56
  useLocalLayoutConfig: false,
55
57
  layouts,