@gitpagedocs/cli 1.1.44
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 +169 -0
- package/ai/application/ai-command.ts +24 -0
- package/ai/application/ai-provider-factory.ts +22 -0
- package/ai/application/run-ai-cli-command.ts +136 -0
- package/ai/config.ts +22 -0
- package/ai/core/models/ai-cli-config.ts +31 -0
- package/ai/core/ports/illm-provider.ts +32 -0
- package/ai/infrastructure/ai-config-file.ts +32 -0
- package/ai/infrastructure/file-system-adapter.ts +83 -0
- package/ai/infrastructure/llm/tools-llm-provider.ts +54 -0
- package/ai/presentation/ai-prompts.ts +239 -0
- package/application/config-only/handler.mjs +55 -0
- package/application/home/handler.mjs +83 -0
- package/application/ports/cli-runtime-ports.ts +87 -0
- package/application/report/config-only-reporter.mjs +62 -0
- package/application/use-cases/dispatch-mode.ts +18 -0
- package/builders/config-orchestrator.mjs +40 -0
- package/builders/root-config-builder.mjs +54 -0
- package/builders/route-builders.mjs +111 -0
- package/builders/source-viewer.mjs +330 -0
- package/builders/theme-template.mjs +75 -0
- package/builders/version-config-builder.mjs +151 -0
- package/content/docs-en.mjs +542 -0
- package/content/docs-es.mjs +459 -0
- package/content/docs-pt.mjs +459 -0
- package/content/docs.mjs +9 -0
- package/contracts/doc-versions.mjs +1 -0
- package/contracts/index.mjs +1 -0
- package/data/default-site-config.mjs +312 -0
- package/data/i18n-langmenu.mjs +122 -0
- package/data/i18n-translations.mjs +20 -0
- package/data/layouts.mjs +353 -0
- package/data/path-mappings.mjs +34 -0
- package/data/route-metas.mjs +73 -0
- package/data/theme-colors.mjs +387 -0
- package/data/urls.mjs +7 -0
- package/data/version-constants.mjs +3 -0
- package/domain/models/cli-options.ts +18 -0
- package/domain/services/sanitize-segment.mjs +7 -0
- package/home/constants.mjs +4 -0
- package/home/home-file-writer.mjs +20 -0
- package/home/templates.mjs +48 -0
- package/index.mjs +26 -0
- package/infrastructure/config-only/runtime.mjs +17 -0
- package/infrastructure/home/runtime.mjs +37 -0
- package/package.json +60 -0
- package/presentation/commands/ai-info.ts +57 -0
- package/presentation/commands/config-info.ts +24 -0
- package/presentation/commands/diagnostics.ts +70 -0
- package/presentation/commands/docs.ts +37 -0
- package/presentation/commands/mcp.ts +20 -0
- package/presentation/commands/pages.ts +91 -0
- package/presentation/commands/run-command.ts +62 -0
- package/presentation/index.ts +108 -0
- package/presentation/options/parser.ts +104 -0
- package/presentation/options/resolver.ts +25 -0
- package/presentation/options/schema.ts +10 -0
- package/presentation/ui/banner.ts +13 -0
- package/presentation/ui/clack.ts +74 -0
- package/presentation/ui/prompts.ts +64 -0
- package/runtime/doc-path-resolver.mjs +57 -0
- package/runtime/git-ops.mjs +110 -0
- package/runtime/output.mjs +122 -0
- package/runtime/workflow.mjs +144 -0
- package/start.mjs +12 -0
- package/ui/logger.mjs +9 -0
package/README.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# gitpagedocs (CLI)
|
|
2
|
+
|
|
3
|
+
The **published npm package** of the [Git Page Docs](../README.md) monorepo. It scaffolds the `gitpagedocs/` docs contract, generates docs with AI, configures GitHub Pages, and runs the MCP server.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install -g gitpagedocs # global
|
|
7
|
+
gitpagedocs # generate gitpagedocs/ config (config-only)
|
|
8
|
+
|
|
9
|
+
# or one-off, no install:
|
|
10
|
+
npx gitpagedocs
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
> This package ships its TypeScript sources and runs them via `tsx`; it depends on the workspace packages `@gitpagedocs/tools` and `@gitpagedocs/mcp` (published alongside it).
|
|
14
|
+
|
|
15
|
+
## Commands
|
|
16
|
+
|
|
17
|
+
| Command | Description |
|
|
18
|
+
|---|---|
|
|
19
|
+
| `gitpagedocs` | Generate `gitpagedocs/` config + versioned docs (config-only) |
|
|
20
|
+
| `gitpagedocs --layoutconfig` | Also emit local layout templates in `gitpagedocs/layouts/` |
|
|
21
|
+
| `gitpagedocs --push --owner <o> --repo <r> [--path <sub>]` | Generate, configure Pages, create workflow, commit + push |
|
|
22
|
+
| `gitpagedocs --home` | Standalone distribution (`gitpagedocshome/`: static site + .env + Dockerfile) |
|
|
23
|
+
| `gitpagedocs -i` / `--interactive` | Interactive prompts |
|
|
24
|
+
| `gitpagedocs ai` | Interactive AI documentation generator (see below) |
|
|
25
|
+
| `gitpagedocs provider [id]` · `models [provider]` | List AI providers / catalog models |
|
|
26
|
+
| `gitpagedocs document[:repo\|:file\|:folder]` | Generate documentation with AI |
|
|
27
|
+
| `gitpagedocs deploy` / `pages [actions\|deploy]` | Configure GitHub Pages via Actions + push |
|
|
28
|
+
| `gitpagedocs docs` | Refresh managed regions of README/CONTRIBUTING/SECURITY |
|
|
29
|
+
| `gitpagedocs doctor` · `version` · `update` | Diagnostics / version / update hint |
|
|
30
|
+
| `gitpagedocs mcp start` | Start the MCP server over stdio |
|
|
31
|
+
|
|
32
|
+
The AI-CLI implementation lives in `cli/ai/` (relocated from the frontend so the published package is self-contained).
|
|
33
|
+
|
|
34
|
+
# CLI Architecture (Clean Architecture + SOLID)
|
|
35
|
+
|
|
36
|
+
## Layer Overview
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
cli/
|
|
40
|
+
├── index.mjs # Node entry (bootstraps TS presentation via tsx)
|
|
41
|
+
├── package.json # the published `gitpagedocs` package
|
|
42
|
+
├── ai/ # AI documentation CLI (provider/model/paths → markdown)
|
|
43
|
+
├── contracts/ # Stable contracts for external tooling
|
|
44
|
+
│ └── doc-versions.mjs
|
|
45
|
+
│
|
|
46
|
+
├── presentation/ # Interface/composition root
|
|
47
|
+
│ ├── index.ts
|
|
48
|
+
│ ├── options/
|
|
49
|
+
│ └── ui/
|
|
50
|
+
│
|
|
51
|
+
├── application/ # Use-cases and ports
|
|
52
|
+
│ ├── config-only/handler.mjs
|
|
53
|
+
│ ├── home/handler.mjs
|
|
54
|
+
│ ├── report/config-only-reporter.mjs
|
|
55
|
+
│ ├── use-cases/dispatch-mode.ts
|
|
56
|
+
│ └── ports/cli-runtime-ports.ts
|
|
57
|
+
│
|
|
58
|
+
├── domain/ # Business rules
|
|
59
|
+
│ └── services/sanitize-segment.mjs
|
|
60
|
+
│
|
|
61
|
+
├── infrastructure/ # Side effects and adapters
|
|
62
|
+
│ ├── config-only/runtime.mjs
|
|
63
|
+
│ └── home/runtime.mjs
|
|
64
|
+
│
|
|
65
|
+
├── builders/ # Pure config builders
|
|
66
|
+
├── data/ # Constants and metadata
|
|
67
|
+
├── content/ # Static docs content
|
|
68
|
+
├── runtime/ # Low-level runtime helpers
|
|
69
|
+
└── home/ # Home templates/files
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## SOLID Principles
|
|
73
|
+
|
|
74
|
+
| Principle | Application |
|
|
75
|
+
|-----------|-------------|
|
|
76
|
+
| **SRP** | `application/report` only formats messages; handlers only orchestrate |
|
|
77
|
+
| **OCP** | New runtime implementations can be injected through ports |
|
|
78
|
+
| **LSP** | `dispatch-mode` works with any runner implementing `CliCommandRunner` |
|
|
79
|
+
| **ISP** | Split runtime contracts (`ConfigOnlyRuntimePort`, `HomeRuntimePort`) |
|
|
80
|
+
| **DIP** | Use-cases depend on `application/ports`, not concrete fs/git/exec |
|
|
81
|
+
|
|
82
|
+
## Clean Architecture
|
|
83
|
+
|
|
84
|
+
- **Interfaces/Presentation**: parse CLI args, resolve mode, wire dependencies in `presentation/index.ts`
|
|
85
|
+
- **Application**: orchestrate flows with explicit ports, without direct fs/git/exec imports
|
|
86
|
+
- **Domain**: sanitize and core rules with no infrastructure dependencies
|
|
87
|
+
- **Infrastructure**: concrete adapters for runtime side effects
|
|
88
|
+
- **Contracts**: `tools/smoke` consumes `cli/contracts` instead of internal CLI modules
|
|
89
|
+
|
|
90
|
+
## Authorized Routes Pipeline
|
|
91
|
+
|
|
92
|
+
The CLI now generates authorized-route docs and config entries in PT/EN/ES.
|
|
93
|
+
|
|
94
|
+
- Source content: `cli/content/docs.mjs` (`authorizedRoutes` key per language)
|
|
95
|
+
- Route metadata: `cli/data/route-metas.mjs` (`ROUTE_META_ID6`)
|
|
96
|
+
- Route path mapping: `cli/data/path-mappings.mjs` (`authorized-routes.md`)
|
|
97
|
+
- Version config composition: `cli/builders/version-config-builder.mjs`
|
|
98
|
+
- Doc key resolution: `cli/runtime/doc-path-resolver.mjs`
|
|
99
|
+
|
|
100
|
+
Generated artifacts include:
|
|
101
|
+
|
|
102
|
+
- `gitpagedocs/docs/versions/<version>/config.json` with `auth` and route `authorization`
|
|
103
|
+
- `gitpagedocs/docs/versions/<version>/{pt,en,es}/authorized-routes.md`
|
|
104
|
+
- `menus-header-md` entry dedicated to Authorized Routes
|
|
105
|
+
|
|
106
|
+
## GitHub Pages Push Paths
|
|
107
|
+
|
|
108
|
+
- Default project-site publish (`--push` without `--path`) targets `/<repo>/`.
|
|
109
|
+
- Custom project subpath publish (`--push --path <segment>`) targets `/<repo>/<segment>/`.
|
|
110
|
+
- `--path` accepts a single sanitized segment (letters, numbers, `.`, `_`, `-`).
|
|
111
|
+
|
|
112
|
+
Example:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
npx gitpagedocs --owner vidigal-code --repo energy-bill-ai-parser --path git-docs --push
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Expected URL after deployment:
|
|
119
|
+
|
|
120
|
+
```text
|
|
121
|
+
https://vidigal-code.github.io/energy-bill-ai-parser/git-docs/
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## AI CLI (interactive + config file)
|
|
125
|
+
|
|
126
|
+
The CLI now includes a dedicated interactive AI mode:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
npx gitpagedocs ai
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### What it does
|
|
133
|
+
|
|
134
|
+
- asks provider (`openai`, `claude`, `gemini`, `ollama`)
|
|
135
|
+
- asks API key or Ollama URL
|
|
136
|
+
- asks paths to scan (supports one or many paths, including other repositories)
|
|
137
|
+
- generates markdown documentation in `pt`, `en`, `es`
|
|
138
|
+
- optionally persists config in `.gitpagedocsconfig`
|
|
139
|
+
- optionally runs standard `gitpagedocs` scaffolding after AI generation
|
|
140
|
+
|
|
141
|
+
### `.gitpagedocsconfig` (manual mode)
|
|
142
|
+
|
|
143
|
+
You can create/edit this file manually and then run `npx gitpagedocs ai`:
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"version": 1,
|
|
148
|
+
"ai": {
|
|
149
|
+
"provider": "openai",
|
|
150
|
+
"model": "gpt-4o-mini",
|
|
151
|
+
"apiKey": "<YOUR_API_KEY>",
|
|
152
|
+
"paths": ["src", "cli", "../another-repo/src"],
|
|
153
|
+
"languages": ["pt", "en", "es"],
|
|
154
|
+
"outputDir": "gitpagedocs/docs",
|
|
155
|
+
"filePrefix": "ai-generated",
|
|
156
|
+
"contextPrompt": "Você é um redator técnico sênior..."
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
For Ollama, use `baseUrl` instead of `apiKey`.
|
|
162
|
+
|
|
163
|
+
### Interactive fallback
|
|
164
|
+
|
|
165
|
+
If a directory is not found, CLI offers fallback choices:
|
|
166
|
+
|
|
167
|
+
- fix paths and retry
|
|
168
|
+
- skip missing paths and continue
|
|
169
|
+
- abort safely
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file ai-command.ts
|
|
3
|
+
* @description Application service for running AI commands. Decoupled from concrete LLMs.
|
|
4
|
+
*/
|
|
5
|
+
import { ILLMProvider } from '../core/ports/illm-provider';
|
|
6
|
+
|
|
7
|
+
export class AiCommandService {
|
|
8
|
+
constructor(private provider: ILLMProvider) { }
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Executes the AI Documentation Generation for a specific content
|
|
12
|
+
*/
|
|
13
|
+
async runGeneration(content: string, contextPrompt?: string): Promise<string> {
|
|
14
|
+
return await this.provider.generateDocumentation(content, contextPrompt);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Executes a CLI chat prompt
|
|
19
|
+
*/
|
|
20
|
+
async runChat(prompt: string): Promise<string> {
|
|
21
|
+
const messages = [{ role: 'user' as const, content: prompt }];
|
|
22
|
+
return await this.provider.chat(messages);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AiProviderId } from "../config";
|
|
2
|
+
import type { ILLMProvider } from "../core/ports/illm-provider";
|
|
3
|
+
import { ToolsLlmProvider } from "../infrastructure/llm/tools-llm-provider";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Creates the CLI LLM provider. Backed by the shared @gitpagedocs/tools AI core
|
|
7
|
+
* (one registry-driven implementation for every provider) — no per-provider
|
|
8
|
+
* switch. The legacy provider id (openai/claude/gemini/ollama) is mapped to the
|
|
9
|
+
* catalog inside ToolsLlmProvider.
|
|
10
|
+
*/
|
|
11
|
+
export function createAiProvider(input: {
|
|
12
|
+
provider: AiProviderId;
|
|
13
|
+
model: string;
|
|
14
|
+
apiKey?: string;
|
|
15
|
+
baseUrl?: string;
|
|
16
|
+
}): ILLMProvider {
|
|
17
|
+
return new ToolsLlmProvider(input.provider, {
|
|
18
|
+
model: input.model,
|
|
19
|
+
apiKey: input.apiKey,
|
|
20
|
+
baseUrl: input.baseUrl,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { AiCommandService } from "./ai-command";
|
|
3
|
+
import { createAiProvider } from "./ai-provider-factory";
|
|
4
|
+
import { FileSystemAdapter, type FilePayload } from "../infrastructure/file-system-adapter";
|
|
5
|
+
import {
|
|
6
|
+
AiConfigFileRepository,
|
|
7
|
+
} from "../infrastructure/ai-config-file";
|
|
8
|
+
import type { AiCliRunPlan, AiCliRunSummary } from "../core/models/ai-cli-config";
|
|
9
|
+
import { promptMissingDirectories, runAiInteractivePrompt } from "../presentation/ai-prompts";
|
|
10
|
+
|
|
11
|
+
const LANGUAGE_TO_LABEL: Record<string, string> = {
|
|
12
|
+
pt: "Portuguese",
|
|
13
|
+
en: "English",
|
|
14
|
+
es: "Español",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function aggregateFilesPayload(files: FilePayload[]): string {
|
|
18
|
+
return files
|
|
19
|
+
.map((file) => `\n\n--- FILE: ${file.filePath} ---\n\n${file.content}`)
|
|
20
|
+
.join("");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function buildLanguagePrompt(basePrompt: string, language: "pt" | "en" | "es"): string {
|
|
24
|
+
const languageLabel = LANGUAGE_TO_LABEL[language] ?? language;
|
|
25
|
+
return `${basePrompt}\n\nMandatory output rules:\n- Respond 100% in ${languageLabel}.\n- Generate professional markdown for technical documentation.\n- Include architectural overview, layer responsibilities, and next steps.\n- Avoid content outside the scope of the analyzed files.`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function collectFilesWithInteractiveFallback(
|
|
29
|
+
fileSystem: FileSystemAdapter,
|
|
30
|
+
initialPaths: string[],
|
|
31
|
+
): Promise<{ files: FilePayload[]; scanned: string[]; skipped: string[] }> {
|
|
32
|
+
let queue = [...initialPaths];
|
|
33
|
+
let scanned: string[] = [];
|
|
34
|
+
let skipped: string[] = [];
|
|
35
|
+
const files: FilePayload[] = [];
|
|
36
|
+
|
|
37
|
+
while (queue.length > 0) {
|
|
38
|
+
const { existing, missing } = fileSystem.splitExistingDirectories(queue);
|
|
39
|
+
scanned = [...scanned, ...existing];
|
|
40
|
+
|
|
41
|
+
for (const directory of existing) {
|
|
42
|
+
const directoryFiles = await fileSystem.readDirectoryRecursively(directory);
|
|
43
|
+
files.push(...directoryFiles);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!missing.length) break;
|
|
47
|
+
|
|
48
|
+
const fallback = await promptMissingDirectories(missing);
|
|
49
|
+
if (fallback.abort) {
|
|
50
|
+
skipped = [...skipped, ...missing];
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!fallback.replacementPaths.length) {
|
|
55
|
+
skipped = [...skipped, ...missing];
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
queue = fallback.replacementPaths;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
files,
|
|
64
|
+
scanned,
|
|
65
|
+
skipped,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function runPlan(plan: AiCliRunPlan): Promise<AiCliRunSummary> {
|
|
70
|
+
const provider = createAiProvider({
|
|
71
|
+
provider: plan.config.ai.provider,
|
|
72
|
+
model: plan.config.ai.model,
|
|
73
|
+
apiKey: plan.config.ai.apiKey,
|
|
74
|
+
baseUrl: plan.config.ai.baseUrl,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const aiService = new AiCommandService(provider);
|
|
78
|
+
const fileSystem = new FileSystemAdapter();
|
|
79
|
+
|
|
80
|
+
const { files, scanned, skipped } = await collectFilesWithInteractiveFallback(
|
|
81
|
+
fileSystem,
|
|
82
|
+
plan.config.ai.paths,
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
if (!files.length) {
|
|
86
|
+
return {
|
|
87
|
+
scannedDirectories: scanned,
|
|
88
|
+
skippedDirectories: skipped,
|
|
89
|
+
scannedFilesCount: 0,
|
|
90
|
+
outputs: [],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const payload = aggregateFilesPayload(files);
|
|
95
|
+
const outputs: string[] = [];
|
|
96
|
+
|
|
97
|
+
for (const language of plan.config.ai.languages) {
|
|
98
|
+
const prompt = buildLanguagePrompt(plan.config.ai.contextPrompt, language);
|
|
99
|
+
const markdown = await aiService.runGeneration(payload, prompt);
|
|
100
|
+
const outputFile = path.posix.join(
|
|
101
|
+
plan.config.ai.outputDir.replace(/\\/g, "/"),
|
|
102
|
+
`${plan.config.ai.filePrefix}-${plan.config.ai.provider}-${language}.md`,
|
|
103
|
+
);
|
|
104
|
+
await fileSystem.writeDocumentationOutput(markdown, outputFile);
|
|
105
|
+
outputs.push(outputFile);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
scannedDirectories: scanned,
|
|
110
|
+
skippedDirectories: skipped,
|
|
111
|
+
scannedFilesCount: files.length,
|
|
112
|
+
outputs,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export async function runAiCliCommand(options: {
|
|
117
|
+
cwd: string;
|
|
118
|
+
onInfo?: (message: string) => void;
|
|
119
|
+
}): Promise<{ summary: AiCliRunSummary; runConfigScaffold: boolean }> {
|
|
120
|
+
const logInfo = options.onInfo ?? (() => undefined);
|
|
121
|
+
const configRepo = new AiConfigFileRepository(options.cwd);
|
|
122
|
+
|
|
123
|
+
const existingConfig = await configRepo.read();
|
|
124
|
+
const plan = await runAiInteractivePrompt(existingConfig);
|
|
125
|
+
|
|
126
|
+
if (plan.saveConfig) {
|
|
127
|
+
await configRepo.write(plan.config);
|
|
128
|
+
logInfo(`[gitpagedocs:ai] Configuration saved to ${configRepo.getConfigPath()}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const summary = await runPlan(plan);
|
|
132
|
+
return {
|
|
133
|
+
summary,
|
|
134
|
+
runConfigScaffold: plan.runConfigScaffold,
|
|
135
|
+
};
|
|
136
|
+
}
|
package/ai/config.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
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
|
+
export const OLLAMA_DEFAULT_BASE_URL = 'http://localhost:11434';
|
|
@@ -0,0 +1,31 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file illm-provider.ts
|
|
3
|
+
* @description Contract for all LLM implementations ensuring OCP and DIP.
|
|
4
|
+
* Isolates the AI logic from the specific LLM providers (OpenAI, Claude, Ollama, etc.).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface ChatMessage {
|
|
8
|
+
role: 'user' | 'assistant' | 'system';
|
|
9
|
+
content: string;
|
|
10
|
+
type?: 'text' | 'image' | 'audio'; // Supporting multimedia as requested
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ILLMProviderConfig {
|
|
14
|
+
apiKey?: string;
|
|
15
|
+
baseUrl?: string;
|
|
16
|
+
model?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ILLMProvider {
|
|
20
|
+
/**
|
|
21
|
+
* Generates markdown documentation based on file content.
|
|
22
|
+
* @param fileContent Raw content of the source file
|
|
23
|
+
* @param contextPrompt Optional instructions to guide the generation (e.g., FSD standard)
|
|
24
|
+
*/
|
|
25
|
+
generateDocumentation(fileContent: string, contextPrompt?: string): Promise<string>;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Conversational chat support.
|
|
29
|
+
* @param messages Array of chat messages (system context, user history, etc.)
|
|
30
|
+
*/
|
|
31
|
+
chat(messages: ChatMessage[]): Promise<string>;
|
|
32
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
AI_CLI_CONFIG_FILENAME,
|
|
5
|
+
type AiCliConfig,
|
|
6
|
+
} from "../core/models/ai-cli-config";
|
|
7
|
+
|
|
8
|
+
export class AiConfigFileRepository {
|
|
9
|
+
constructor(private readonly cwd: string = process.cwd()) {}
|
|
10
|
+
|
|
11
|
+
getConfigPath(): string {
|
|
12
|
+
return path.join(this.cwd, AI_CLI_CONFIG_FILENAME);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async read(): Promise<AiCliConfig | null> {
|
|
16
|
+
const configPath = this.getConfigPath();
|
|
17
|
+
try {
|
|
18
|
+
const content = await fs.readFile(configPath, "utf-8");
|
|
19
|
+
const parsed = JSON.parse(content) as Partial<AiCliConfig>;
|
|
20
|
+
if (!parsed || parsed.version !== 1 || !parsed.ai) return null;
|
|
21
|
+
return parsed as AiCliConfig;
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async write(config: AiCliConfig): Promise<void> {
|
|
28
|
+
const configPath = this.getConfigPath();
|
|
29
|
+
const data = JSON.stringify(config, null, 2);
|
|
30
|
+
await fs.writeFile(configPath, `${data}\n`, "utf-8");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { statSync } from 'node:fs';
|
|
4
|
+
|
|
5
|
+
export interface FilePayload {
|
|
6
|
+
filePath: string;
|
|
7
|
+
content: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class FileSystemAdapter {
|
|
11
|
+
splitExistingDirectories(targetDirectories: string[]): { existing: string[]; missing: string[] } {
|
|
12
|
+
const existing: string[] = [];
|
|
13
|
+
const missing: string[] = [];
|
|
14
|
+
|
|
15
|
+
for (const targetDirectory of targetDirectories) {
|
|
16
|
+
const absolutePath = path.resolve(process.cwd(), targetDirectory);
|
|
17
|
+
try {
|
|
18
|
+
const stat = statSync(absolutePath);
|
|
19
|
+
if (stat.isDirectory()) {
|
|
20
|
+
existing.push(targetDirectory);
|
|
21
|
+
} else {
|
|
22
|
+
missing.push(targetDirectory);
|
|
23
|
+
}
|
|
24
|
+
} catch {
|
|
25
|
+
missing.push(targetDirectory);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return { existing, missing };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async readDirectoryRecursively(targetDirectory: string): Promise<FilePayload[]> {
|
|
33
|
+
const absolutePath = path.resolve(process.cwd(), targetDirectory);
|
|
34
|
+
const resolvedFiles: FilePayload[] = [];
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
await this.traverse(absolutePath, resolvedFiles);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return resolvedFiles;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private async traverse(currentPath: string, payloadList: FilePayload[]): Promise<void> {
|
|
45
|
+
let stats;
|
|
46
|
+
try {
|
|
47
|
+
stats = await fs.stat(currentPath);
|
|
48
|
+
} catch {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (stats.isDirectory()) {
|
|
53
|
+
const basename = path.basename(currentPath);
|
|
54
|
+
if (['node_modules', '.git', '.next', 'out', 'dist'].includes(basename)) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const entries = await fs.readdir(currentPath);
|
|
59
|
+
for (const entry of entries) {
|
|
60
|
+
await this.traverse(path.join(currentPath, entry), payloadList);
|
|
61
|
+
}
|
|
62
|
+
} else if (stats.isFile()) {
|
|
63
|
+
const ext = path.extname(currentPath);
|
|
64
|
+
const validExtensions = ['.ts', '.tsx', '.js', '.jsx', '.json', '.md', '.css'];
|
|
65
|
+
if (validExtensions.includes(ext)) {
|
|
66
|
+
try {
|
|
67
|
+
const content = await fs.readFile(currentPath, 'utf-8');
|
|
68
|
+
payloadList.push({
|
|
69
|
+
filePath: currentPath,
|
|
70
|
+
content: content
|
|
71
|
+
});
|
|
72
|
+
} catch {
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async writeDocumentationOutput(outputContent: string, targetPath: string = "documentation-output.md"): Promise<void> {
|
|
79
|
+
const absoluteDest = path.resolve(process.cwd(), targetPath);
|
|
80
|
+
await fs.mkdir(path.dirname(absoluteDest), { recursive: true });
|
|
81
|
+
await fs.writeFile(absoluteDest, outputContent, 'utf-8');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { createDefaultFactory, legacyProviderToCatalogId } from "@gitpagedocs/tools/ai";
|
|
2
|
+
import type { AiMessage, ProviderConfig } from "@gitpagedocs/tools/ports";
|
|
3
|
+
import type { ILLMProvider, ChatMessage, ILLMProviderConfig } from "../../core/ports/illm-provider";
|
|
4
|
+
import { DEFAULT_AI_DOC_PROMPT, type AiProviderId } from "../../config";
|
|
5
|
+
|
|
6
|
+
const factory = createDefaultFactory();
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Single CLI LLM provider backed by the shared @gitpagedocs/tools AI core.
|
|
10
|
+
* Replaces the four duplicated CLI provider classes (OpenAI/Claude/Gemini/
|
|
11
|
+
* Ollama) with one adapter over the registry/factory, preserving the
|
|
12
|
+
* ILLMProvider contract used by the AI documentation flow.
|
|
13
|
+
*/
|
|
14
|
+
export class ToolsLlmProvider implements ILLMProvider {
|
|
15
|
+
private readonly providerId;
|
|
16
|
+
private readonly model: string;
|
|
17
|
+
private readonly apiKey?: string;
|
|
18
|
+
private readonly baseUrl?: string;
|
|
19
|
+
|
|
20
|
+
constructor(legacyProvider: AiProviderId, config: ILLMProviderConfig) {
|
|
21
|
+
this.providerId = legacyProviderToCatalogId(legacyProvider);
|
|
22
|
+
this.model = config.model ?? "";
|
|
23
|
+
this.apiKey = config.apiKey;
|
|
24
|
+
this.baseUrl = config.baseUrl;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async generateDocumentation(fileContent: string, contextPrompt?: string): Promise<string> {
|
|
28
|
+
return this.chat([
|
|
29
|
+
{ role: "system", content: contextPrompt || DEFAULT_AI_DOC_PROMPT },
|
|
30
|
+
{ role: "user", content: fileContent },
|
|
31
|
+
]);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async chat(messages: ChatMessage[]): Promise<string> {
|
|
35
|
+
const system = messages
|
|
36
|
+
.filter((m) => m.role === "system")
|
|
37
|
+
.map((m) => m.content)
|
|
38
|
+
.join("\n");
|
|
39
|
+
const aiMessages: AiMessage[] = messages
|
|
40
|
+
.filter((m) => m.role !== "system")
|
|
41
|
+
.map((m) => ({ role: m.role as AiMessage["role"], content: m.content }));
|
|
42
|
+
|
|
43
|
+
const config: ProviderConfig = {
|
|
44
|
+
providerId: this.providerId,
|
|
45
|
+
model: this.model,
|
|
46
|
+
apiKey: this.apiKey,
|
|
47
|
+
baseUrl: this.baseUrl,
|
|
48
|
+
};
|
|
49
|
+
const response = await factory
|
|
50
|
+
.create(this.providerId)
|
|
51
|
+
.generate({ messages: aiMessages, system: system || undefined, maxTokens: 4000 }, config);
|
|
52
|
+
return response.text;
|
|
53
|
+
}
|
|
54
|
+
}
|