@elysiumoss/grepo 0.2.0 โ 0.4.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.
- package/README.md +79 -40
- package/lib/cli.js +30 -2
- package/lib/cli.js.map +1 -1
- package/lib/commands/analyze.js +1 -1
- package/lib/commands/analyze.js.map +1 -1
- package/lib/commands/describe.js +2 -2
- package/lib/commands/describe.js.map +1 -1
- package/lib/commands/readme.js +3 -3
- package/lib/commands/readme.js.map +1 -1
- package/lib/commands/topics.js +2 -2
- package/lib/commands/topics.js.map +1 -1
- package/lib/config.d.ts +1 -1
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +7 -5
- package/lib/config.js.map +1 -1
- package/lib/index.js +1 -1
- package/lib/mermaid.d.ts.map +1 -1
- package/lib/mermaid.js +38 -6
- package/lib/mermaid.js.map +1 -1
- package/lib/prompts/readme.d.ts.map +1 -1
- package/lib/prompts/readme.js +5 -1
- package/lib/prompts/readme.js.map +1 -1
- package/lib/services.d.ts +2 -1
- package/lib/services.d.ts.map +1 -1
- package/lib/services.js +9 -2
- package/lib/services.js.map +1 -1
- package/lib/utils/config-file.d.ts +16 -0
- package/lib/utils/config-file.d.ts.map +1 -0
- package/lib/utils/config-file.js +80 -0
- package/lib/utils/config-file.js.map +1 -0
- package/lib/utils/gemini.d.ts.map +1 -1
- package/lib/utils/gemini.js +7 -3
- package/lib/utils/gemini.js.map +1 -1
- package/lib/utils/github.d.ts +1 -0
- package/lib/utils/github.d.ts.map +1 -1
- package/lib/utils/github.js +7 -0
- package/lib/utils/github.js.map +1 -1
- package/lib/utils/gitingest.d.ts +1 -1
- package/lib/utils/gitingest.d.ts.map +1 -1
- package/lib/utils/gitingest.js +2 -2
- package/lib/utils/gitingest.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,61 +1,100 @@
|
|
|
1
|
-
|
|
1
|
+
# grepo
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
|
-
|
|
5
|
-
Hooray!
|
|
4
|
+
<b>An agentic CLI tool for analyzing, describing, and generating documentation for GitHub repositories.</b>
|
|
6
5
|
</p>
|
|
7
6
|
|
|
8
7
|
<p align="center">
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
<a href="#contributors" target="_blank"><img alt="๐ช All Contributors: 2" src="https://img.shields.io/badge/%F0%9F%91%AA_all_contributors-2-21bb42.svg" /></a>
|
|
12
|
-
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
|
13
|
-
<!-- prettier-ignore-end -->
|
|
14
|
-
<a href="https://github.com/ElysiumOSS/package-template/blob/main/.github/CODE_OF_CONDUCT.md" target="_blank"><img alt="๐ค Code of Conduct: Kept" src="https://img.shields.io/badge/%F0%9F%A4%9D_code_of_conduct-kept-21bb42" /></a>
|
|
15
|
-
<a href="https://codecov.io/gh/ElysiumOSS/package-template" target="_blank"><img alt="๐งช Coverage" src="https://img.shields.io/codecov/c/github/ElysiumOSS/package-template?label=%F0%9F%A7%AA%20coverage" /></a>
|
|
16
|
-
<a href="https://github.com/ElysiumOSS/package-template/blob/main/LICENSE.md" target="_blank"><img alt="๐ License: MIT" src="https://img.shields.io/badge/%F0%9F%93%9D_license-MIT-21bb42.svg" /></a>
|
|
17
|
-
<a href="http://npmjs.com/package/package-template" target="_blank"><img alt="๐ฆ npm version" src="https://img.shields.io/npm/v/package-template?color=21bb42&label=%F0%9F%93%A6%20npm" /></a>
|
|
18
|
-
<img alt="๐ช TypeScript: Strict" src="https://img.shields.io/badge/%F0%9F%92%AA_typescript-strict-21bb42.svg" />
|
|
8
|
+
<a href="https://github.com/ElysiumOSS/grepo/blob/main/LICENSE.md" target="_blank"><img alt="๐ License: MIT" src="https://img.shields.io/badge/%F0%9F%93%9D_license-MIT-21bb42.svg" /></a>
|
|
9
|
+
<a href="https://npmjs.com/package/@elysiumoss/grepo" target="_blank"><img alt="๐ฆ npm version" src="https://img.shields.io/npm/v/@elysiumoss/grepo?color=21bb42&label=%F0%9F%93%A6%20npm" /></a>
|
|
19
10
|
</p>
|
|
20
11
|
|
|
12
|
+
## Overview
|
|
13
|
+
|
|
14
|
+
`grepo` automates the heavy lifting of repository maintenance. By integrating with LLM providers like Google Gemini, it intelligently analyzes your codebase to generate professional READMEs, suggest relevant repository topics, and craft repository descriptions.
|
|
15
|
+
|
|
16
|
+
```mermaid
|
|
17
|
+
flowchart TD
|
|
18
|
+
User["User Command"] --> CLI["grepo CLI"]
|
|
19
|
+
CLI --> GH["GitHub API / Gitingest"]
|
|
20
|
+
GH -->|Repo Context| CLI
|
|
21
|
+
CLI --> LLM["LLM (Gemini)"]
|
|
22
|
+
LLM -->|Analysis/Doc Content| Generator["Documentation Generator"]
|
|
23
|
+
Generator --> Output["Output (README/Mermaid)"]
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
Ensure you have [Bun](https://bun.sh/) installed, then install `grepo` globally:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
bun add -g @elysiumoss/grepo
|
|
32
|
+
```
|
|
33
|
+
|
|
21
34
|
## Usage
|
|
22
35
|
|
|
23
|
-
|
|
24
|
-
|
|
36
|
+
Generate a new `README.md` for a repository:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
grepo readme https://github.com/owner/repo --format md --push
|
|
25
40
|
```
|
|
26
41
|
|
|
27
|
-
|
|
28
|
-
import { greet } from "package-template";
|
|
42
|
+
Automatically update repository topics based on code analysis:
|
|
29
43
|
|
|
30
|
-
|
|
44
|
+
```bash
|
|
45
|
+
grepo topics https://github.com/owner/repo --apply --merge
|
|
31
46
|
```
|
|
32
47
|
|
|
33
|
-
##
|
|
48
|
+
## CLI Reference
|
|
49
|
+
|
|
50
|
+
| Command | Description |
|
|
51
|
+
| :--- | :--- |
|
|
52
|
+
| `readme` | Generate and optionally push a README documentation file |
|
|
53
|
+
| `topics` | Analyze code and suggest/apply repository topics |
|
|
54
|
+
| `describe` | Generate a repository description and detect homepage URLs |
|
|
55
|
+
| `summary` | Provide a comprehensive summary of the repository |
|
|
56
|
+
| `tech` | List technologies, frameworks, and tools used |
|
|
57
|
+
| `improve` | Suggest 5 specific, actionable improvements |
|
|
58
|
+
|
|
59
|
+
**Options:**
|
|
60
|
+
- `--format md|mdx`: Output format (default: `md`)
|
|
61
|
+
- `--push`: Push the generated file directly to the GitHub repository
|
|
62
|
+
- `--apply`: Apply changes (topics/description) directly to the GitHub API
|
|
63
|
+
- `--dry-run`: Preview changes without writing or pushing
|
|
64
|
+
- `--model <id>`: Manually specify the LLM model to use
|
|
34
65
|
|
|
35
|
-
|
|
36
|
-
Thanks! ๐
|
|
66
|
+
## Configuration
|
|
37
67
|
|
|
38
|
-
|
|
68
|
+
`grepo` requires authentication for repository access and AI analysis. Configure these via environment variables or a `.env` file:
|
|
39
69
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
</tbody>
|
|
51
|
-
</table>
|
|
70
|
+
- `GEMINI_API_KEY`: Required for AI code analysis.
|
|
71
|
+
- `GH_TOKEN` or `GITHUB_TOKEN`: Required for pushing files, updating topics, or repository descriptions.
|
|
72
|
+
|
|
73
|
+
**Example `.env` file:**
|
|
74
|
+
```env
|
|
75
|
+
GEMINI_API_KEY=AIzaSy...
|
|
76
|
+
GH_TOKEN=ghp_...
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Development
|
|
52
80
|
|
|
53
|
-
|
|
54
|
-
|
|
81
|
+
1. **Clone the repo:**
|
|
82
|
+
```bash
|
|
83
|
+
git clone https://github.com/ElysiumOSS/grepo
|
|
84
|
+
cd grepo
|
|
85
|
+
bun install
|
|
86
|
+
```
|
|
87
|
+
2. **Build the project:**
|
|
88
|
+
```bash
|
|
89
|
+
bun run build
|
|
90
|
+
```
|
|
91
|
+
3. **Run tests:**
|
|
92
|
+
```bash
|
|
93
|
+
bun run test
|
|
94
|
+
```
|
|
55
95
|
|
|
56
|
-
|
|
57
|
-
<!-- spellchecker: enable -->
|
|
96
|
+
See [`.github/CONTRIBUTING.md`](./.github/CONTRIBUTING.md) and [`.github/DEVELOPMENT.md`](./.github/DEVELOPMENT.md) for detailed guidelines.
|
|
58
97
|
|
|
59
|
-
|
|
98
|
+
## License
|
|
60
99
|
|
|
61
|
-
|
|
100
|
+
This project is licensed under the [MIT License](LICENSE.md).
|
package/lib/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { buildConfig, loadEnv } from "./config.js";
|
|
3
2
|
import { Logger as Logger$1 } from "./utils/logger.js";
|
|
3
|
+
import { loadConfigFile, promptConfigSetup } from "./utils/config-file.js";
|
|
4
|
+
import { buildConfig, loadEnv } from "./config.js";
|
|
4
5
|
import { GeminiLive, GitHubLive } from "./services.js";
|
|
5
6
|
import { Effect, Layer } from "effect";
|
|
6
7
|
//#region src/cli.ts
|
|
@@ -24,6 +25,15 @@ import { Effect, Layer } from "effect";
|
|
|
24
25
|
const logger = new Logger$1("GREPO");
|
|
25
26
|
await loadEnv();
|
|
26
27
|
const argv = process.argv.slice(2);
|
|
28
|
+
if (argv.length > 0) {
|
|
29
|
+
if (!!!(process.env.GEMINI_API_KEY ?? process.env.GOOGLE_API_KEY)) {
|
|
30
|
+
if (!loadConfigFile().geminiApiKey) {
|
|
31
|
+
const setup = await promptConfigSetup();
|
|
32
|
+
if (setup.geminiApiKey) process.env.GEMINI_API_KEY = setup.geminiApiKey;
|
|
33
|
+
if (setup.githubToken) process.env.GITHUB_TOKEN = setup.githubToken;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
27
37
|
if (argv.length === 0) {
|
|
28
38
|
console.log(`Usage: grepo <command> <github-url> [options]
|
|
29
39
|
|
|
@@ -38,7 +48,25 @@ Commands:
|
|
|
38
48
|
Run 'grepo <command> --help' for more information.`);
|
|
39
49
|
process.exit(0);
|
|
40
50
|
}
|
|
41
|
-
const
|
|
51
|
+
const parsedConfig = buildConfig(argv);
|
|
52
|
+
let resolvedBranch = parsedConfig.branch;
|
|
53
|
+
if (!resolvedBranch) try {
|
|
54
|
+
const { GitHubClient } = await import("./utils/github.js");
|
|
55
|
+
const { parseGitHubUrl } = await import("./utils/validation.js");
|
|
56
|
+
const { owner, repo } = parseGitHubUrl(parsedConfig.repoUrl);
|
|
57
|
+
resolvedBranch = await new GitHubClient(parsedConfig.githubToken).getDefaultBranch(owner, repo);
|
|
58
|
+
logger.info(`Detected default branch: ${resolvedBranch}`);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
resolvedBranch = "main";
|
|
61
|
+
logger.warn("Could not detect default branch, falling back to 'main'", {
|
|
62
|
+
error: err instanceof Error ? err.message : String(err),
|
|
63
|
+
repo: parsedConfig.repoUrl
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
const config = {
|
|
67
|
+
...parsedConfig,
|
|
68
|
+
branch: resolvedBranch
|
|
69
|
+
};
|
|
42
70
|
const layers = Layer.merge(GeminiLive(config.geminiApiKey), GitHubLive(config.githubToken));
|
|
43
71
|
let run;
|
|
44
72
|
switch (config.command) {
|
package/lib/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","names":["Logger"],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n *\n * Copyright 2026 Mike Odnis\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\nimport { Effect, Layer } from \"effect\";\n\nimport type { GrepoConfig } from \"./config.js\";\nimport { buildConfig, loadEnv } from \"./config.js\";\nimport type {\n\tGeminiError,\n\tGitHubError,\n\tGitIngestError,\n\tGrepoValidationError,\n} from \"./errors.js\";\nimport type { Gemini, GitHub } from \"./services.js\";\nimport { GeminiLive, GitHubLive } from \"./services.js\";\nimport { Logger } from \"./utils/logger.js\";\n\nconst logger = new Logger(\"GREPO\");\n\nawait loadEnv();\n\nconst argv = process.argv.slice(2);\n\nif (argv.length === 0) {\n\tconsole.log(`Usage: grepo <command> <github-url> [options]\n\nCommands:\n readme Generate README documentation\n topics Generate and apply repository topics\n describe Generate repository description and homepage URL\n summary Summarize repository\n tech List technologies used\n improve Suggest improvements\n\nRun 'grepo <command> --help' for more information.`);\n\tprocess.exit(0);\n}\n\nconst
|
|
1
|
+
{"version":3,"file":"cli.js","names":["Logger"],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n *\n * Copyright 2026 Mike Odnis\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\nimport { Effect, Layer } from \"effect\";\n\nimport type { GrepoConfig } from \"./config.js\";\nimport { buildConfig, loadEnv } from \"./config.js\";\nimport type {\n\tGeminiError,\n\tGitHubError,\n\tGitIngestError,\n\tGrepoValidationError,\n} from \"./errors.js\";\nimport type { Gemini, GitHub } from \"./services.js\";\nimport { GeminiLive, GitHubLive } from \"./services.js\";\nimport { loadConfigFile, promptConfigSetup } from \"./utils/config-file.js\";\nimport { Logger } from \"./utils/logger.js\";\n\nconst logger = new Logger(\"GREPO\");\n\nawait loadEnv();\n\nconst argv = process.argv.slice(2);\n\n// Interactive setup if no API keys are configured\nif (argv.length > 0) {\n\tconst hasGeminiKey = !!(\n\t\tprocess.env.GEMINI_API_KEY ?? process.env.GOOGLE_API_KEY\n\t);\n\tif (!hasGeminiKey) {\n\t\tconst configFile = loadConfigFile();\n\t\tif (!configFile.geminiApiKey) {\n\t\t\tconst setup = await promptConfigSetup();\n\t\t\tif (setup.geminiApiKey) {\n\t\t\t\tprocess.env.GEMINI_API_KEY = setup.geminiApiKey;\n\t\t\t}\n\t\t\tif (setup.githubToken) {\n\t\t\t\tprocess.env.GITHUB_TOKEN = setup.githubToken;\n\t\t\t}\n\t\t}\n\t}\n}\n\nif (argv.length === 0) {\n\tconsole.log(`Usage: grepo <command> <github-url> [options]\n\nCommands:\n readme Generate README documentation\n topics Generate and apply repository topics\n describe Generate repository description and homepage URL\n summary Summarize repository\n tech List technologies used\n improve Suggest improvements\n\nRun 'grepo <command> --help' for more information.`);\n\tprocess.exit(0);\n}\n\nconst parsedConfig = buildConfig(argv);\n\n// Auto-detect default branch from GitHub API if not explicitly set\nlet resolvedBranch = parsedConfig.branch;\nif (!resolvedBranch) {\n\ttry {\n\t\tconst { GitHubClient } = await import(\"./utils/github.js\");\n\t\tconst { parseGitHubUrl } = await import(\"./utils/validation.js\");\n\t\tconst { owner, repo } = parseGitHubUrl(parsedConfig.repoUrl);\n\t\tconst client = new GitHubClient(parsedConfig.githubToken);\n\t\tresolvedBranch = await client.getDefaultBranch(owner, repo);\n\t\tlogger.info(`Detected default branch: ${resolvedBranch}`);\n\t} catch (err) {\n\t\tresolvedBranch = \"main\";\n\t\tlogger.warn(\"Could not detect default branch, falling back to 'main'\", {\n\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\trepo: parsedConfig.repoUrl,\n\t\t});\n\t}\n}\n\nconst config = { ...parsedConfig, branch: resolvedBranch };\n\nconst layers = Layer.merge(\n\tGeminiLive(config.geminiApiKey),\n\tGitHubLive(config.githubToken),\n);\n\ntype CommandRunner = (\n\tconfig: GrepoConfig,\n) => Effect.Effect<\n\tvoid,\n\tGeminiError | GitHubError | GitIngestError | GrepoValidationError,\n\tGemini | GitHub\n>;\n\nlet run: CommandRunner;\n\nswitch (config.command) {\n\tcase \"readme\":\n\t\trun = (await import(\"./commands/readme.js\")).run;\n\t\tbreak;\n\tcase \"topics\":\n\t\trun = (await import(\"./commands/topics.js\")).run;\n\t\tbreak;\n\tcase \"describe\":\n\t\trun = (await import(\"./commands/describe.js\")).run;\n\t\tbreak;\n\tcase \"summary\":\n\tcase \"tech\":\n\tcase \"improve\":\n\t\trun = (await import(\"./commands/analyze.js\")).run;\n\t\tbreak;\n}\n\nawait Effect.runPromise(\n\trun(config).pipe(\n\t\tEffect.provide(layers),\n\t\tEffect.catchTags({\n\t\t\tGeminiError: (e) =>\n\t\t\t\tEffect.sync(() => {\n\t\t\t\t\tlogger.error(`Gemini Error: ${e.message}`);\n\t\t\t\t\tprocess.exit(1);\n\t\t\t\t}),\n\t\t\tGitHubError: (e) =>\n\t\t\t\tEffect.sync(() => {\n\t\t\t\t\tlogger.error(\n\t\t\t\t\t\t`GitHub Error (${e.endpoint}): ${e.message}`,\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\t{ status: e.statusCode },\n\t\t\t\t\t);\n\t\t\t\t\tprocess.exit(1);\n\t\t\t\t}),\n\t\t\tGitIngestError: (e) =>\n\t\t\t\tEffect.sync(() => {\n\t\t\t\t\tlogger.error(`GitIngest Error: ${e.message}`);\n\t\t\t\t\tprocess.exit(1);\n\t\t\t\t}),\n\t\t\tGrepoValidationError: (e) =>\n\t\t\t\tEffect.sync(() => {\n\t\t\t\t\tlogger.error(`Validation Error: ${e.message}`, undefined, {\n\t\t\t\t\t\tfield: e.field,\n\t\t\t\t\t});\n\t\t\t\t\tprocess.exit(1);\n\t\t\t\t}),\n\t\t}),\n\t),\n).catch((error) => {\n\tlogger.error(\"An unexpected error occurred\", error);\n\tprocess.exit(1);\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAkCA,MAAM,SAAS,IAAIA,SAAO,QAAQ;AAElC,MAAM,SAAS;AAEf,MAAM,OAAO,QAAQ,KAAK,MAAM,EAAE;AAGlC,IAAI,KAAK,SAAS;KAIb,CAHiB,CAAC,EACrB,QAAQ,IAAI,kBAAkB,QAAQ,IAAI;MAItC,CADe,gBAAgB,CACnB,cAAc;GAC7B,MAAM,QAAQ,MAAM,mBAAmB;AACvC,OAAI,MAAM,aACT,SAAQ,IAAI,iBAAiB,MAAM;AAEpC,OAAI,MAAM,YACT,SAAQ,IAAI,eAAe,MAAM;;;;AAMrC,IAAI,KAAK,WAAW,GAAG;AACtB,SAAQ,IAAI;;;;;;;;;;oDAUuC;AACnD,SAAQ,KAAK,EAAE;;AAGhB,MAAM,eAAe,YAAY,KAAK;AAGtC,IAAI,iBAAiB,aAAa;AAClC,IAAI,CAAC,eACJ,KAAI;CACH,MAAM,EAAE,iBAAiB,MAAM,OAAO;CACtC,MAAM,EAAE,mBAAmB,MAAM,OAAO;CACxC,MAAM,EAAE,OAAO,SAAS,eAAe,aAAa,QAAQ;AAE5D,kBAAiB,MADF,IAAI,aAAa,aAAa,YAAY,CAC3B,iBAAiB,OAAO,KAAK;AAC3D,QAAO,KAAK,4BAA4B,iBAAiB;SACjD,KAAK;AACb,kBAAiB;AACjB,QAAO,KAAK,2DAA2D;EACtE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;EACvD,MAAM,aAAa;EACnB,CAAC;;AAIJ,MAAM,SAAS;CAAE,GAAG;CAAc,QAAQ;CAAgB;AAE1D,MAAM,SAAS,MAAM,MACpB,WAAW,OAAO,aAAa,EAC/B,WAAW,OAAO,YAAY,CAC9B;AAUD,IAAI;AAEJ,QAAQ,OAAO,SAAf;CACC,KAAK;AACJ,SAAO,MAAM,OAAO,yBAAyB;AAC7C;CACD,KAAK;AACJ,SAAO,MAAM,OAAO,yBAAyB;AAC7C;CACD,KAAK;AACJ,SAAO,MAAM,OAAO,2BAA2B;AAC/C;CACD,KAAK;CACL,KAAK;CACL,KAAK;AACJ,SAAO,MAAM,OAAO,0BAA0B;AAC9C;;AAGF,MAAM,OAAO,WACZ,IAAI,OAAO,CAAC,KACX,OAAO,QAAQ,OAAO,EACtB,OAAO,UAAU;CAChB,cAAc,MACb,OAAO,WAAW;AACjB,SAAO,MAAM,iBAAiB,EAAE,UAAU;AAC1C,UAAQ,KAAK,EAAE;GACd;CACH,cAAc,MACb,OAAO,WAAW;AACjB,SAAO,MACN,iBAAiB,EAAE,SAAS,KAAK,EAAE,WACnC,KAAA,GACA,EAAE,QAAQ,EAAE,YAAY,CACxB;AACD,UAAQ,KAAK,EAAE;GACd;CACH,iBAAiB,MAChB,OAAO,WAAW;AACjB,SAAO,MAAM,oBAAoB,EAAE,UAAU;AAC7C,UAAQ,KAAK,EAAE;GACd;CACH,uBAAuB,MACtB,OAAO,WAAW;AACjB,SAAO,MAAM,qBAAqB,EAAE,WAAW,KAAA,GAAW,EACzD,OAAO,EAAE,OACT,CAAC;AACF,UAAQ,KAAK,EAAE;GACd;CACH,CAAC,CACF,CACD,CAAC,OAAO,UAAU;AAClB,QAAO,MAAM,gCAAgC,MAAM;AACnD,SAAQ,KAAK,EAAE;EACd"}
|
package/lib/commands/analyze.js
CHANGED
|
@@ -31,7 +31,7 @@ const run = (config) => Effect.gen(function* () {
|
|
|
31
31
|
const analysisType = config.command;
|
|
32
32
|
displayHeader(`grepo ${analysisType}`, { Repository: config.repoUrl });
|
|
33
33
|
logger.info("Fetching repository content via GitIngest...");
|
|
34
|
-
const repoData = yield* fetchRepo(config.repoUrl);
|
|
34
|
+
const repoData = yield* fetchRepo(config.repoUrl, config.githubToken);
|
|
35
35
|
logger.success("Content fetched successfully");
|
|
36
36
|
logger.info(`Running ${analysisType} analysis via Gemini...`);
|
|
37
37
|
const prompt = `${PROMPTS[analysisType]}\n\nTree:\n${repoData.tree}\n\nContent:\n${repoData.content.slice(0, 4e3)}`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyze.js","names":["Logger"],"sources":["../../src/commands/analyze.ts"],"sourcesContent":["/**\n *\n * Copyright 2026 Mike Odnis\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\nimport { Effect } from \"effect\";\n\nimport type { GrepoConfig } from \"../config.js\";\nimport { fetchRepo, Gemini } from \"../services.js\";\nimport { displayHeader, SEPARATOR } from \"../utils/args.js\";\nimport { Logger } from \"../utils/logger.js\";\n\nconst logger = new Logger(\"GREPO:ANALYZE\");\n\ntype AnalysisType = \"improve\" | \"summary\" | \"tech\";\n\nconst PROMPTS: Record<AnalysisType, string> = {\n\timprove: \"Suggest 5 specific, actionable improvements for this repository.\",\n\tsummary: \"Provide a comprehensive 2-3 paragraph summary of this repository.\",\n\ttech: \"List all technologies, frameworks, and tools used in this repository as a categorized markdown list.\",\n};\n\nexport const run = (config: GrepoConfig) =>\n\tEffect.gen(function* () {\n\t\tconst gemini = yield* Gemini;\n\t\tconst analysisType = config.command as AnalysisType;\n\n\t\tdisplayHeader(`grepo ${analysisType}`, {\n\t\t\tRepository: config.repoUrl,\n\t\t});\n\n\t\tlogger.info(\"Fetching repository content via GitIngest...\");\n\t\tconst repoData = yield* fetchRepo(config.repoUrl);\n\t\tlogger.success(\"Content fetched successfully\");\n\n\t\tlogger.info(`Running ${analysisType} analysis via Gemini...`);\n\t\tconst prompt = `${PROMPTS[analysisType]}\\n\\nTree:\\n${repoData.tree}\\n\\nContent:\\n${repoData.content.slice(0, 4000)}`;\n\t\tconst result = yield* gemini.generateContent(prompt);\n\t\tlogger.success(\"Analysis complete\");\n\n\t\tconsole.log();\n\t\tconsole.log(SEPARATOR);\n\t\tconsole.log(result);\n\t\tconsole.log(SEPARATOR);\n\t\tconsole.log();\n\t});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAyBA,MAAM,SAAS,IAAIA,SAAO,gBAAgB;AAI1C,MAAM,UAAwC;CAC7C,SAAS;CACT,SAAS;CACT,MAAM;CACN;AAED,MAAa,OAAO,WACnB,OAAO,IAAI,aAAa;CACvB,MAAM,SAAS,OAAO;CACtB,MAAM,eAAe,OAAO;AAE5B,eAAc,SAAS,gBAAgB,EACtC,YAAY,OAAO,SACnB,CAAC;AAEF,QAAO,KAAK,+CAA+C;CAC3D,MAAM,WAAW,OAAO,UAAU,OAAO,
|
|
1
|
+
{"version":3,"file":"analyze.js","names":["Logger"],"sources":["../../src/commands/analyze.ts"],"sourcesContent":["/**\n *\n * Copyright 2026 Mike Odnis\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\nimport { Effect } from \"effect\";\n\nimport type { GrepoConfig } from \"../config.js\";\nimport { fetchRepo, Gemini } from \"../services.js\";\nimport { displayHeader, SEPARATOR } from \"../utils/args.js\";\nimport { Logger } from \"../utils/logger.js\";\n\nconst logger = new Logger(\"GREPO:ANALYZE\");\n\ntype AnalysisType = \"improve\" | \"summary\" | \"tech\";\n\nconst PROMPTS: Record<AnalysisType, string> = {\n\timprove: \"Suggest 5 specific, actionable improvements for this repository.\",\n\tsummary: \"Provide a comprehensive 2-3 paragraph summary of this repository.\",\n\ttech: \"List all technologies, frameworks, and tools used in this repository as a categorized markdown list.\",\n};\n\nexport const run = (config: GrepoConfig) =>\n\tEffect.gen(function* () {\n\t\tconst gemini = yield* Gemini;\n\t\tconst analysisType = config.command as AnalysisType;\n\n\t\tdisplayHeader(`grepo ${analysisType}`, {\n\t\t\tRepository: config.repoUrl,\n\t\t});\n\n\t\tlogger.info(\"Fetching repository content via GitIngest...\");\n\t\tconst repoData = yield* fetchRepo(config.repoUrl, config.githubToken);\n\t\tlogger.success(\"Content fetched successfully\");\n\n\t\tlogger.info(`Running ${analysisType} analysis via Gemini...`);\n\t\tconst prompt = `${PROMPTS[analysisType]}\\n\\nTree:\\n${repoData.tree}\\n\\nContent:\\n${repoData.content.slice(0, 4000)}`;\n\t\tconst result = yield* gemini.generateContent(prompt);\n\t\tlogger.success(\"Analysis complete\");\n\n\t\tconsole.log();\n\t\tconsole.log(SEPARATOR);\n\t\tconsole.log(result);\n\t\tconsole.log(SEPARATOR);\n\t\tconsole.log();\n\t});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAyBA,MAAM,SAAS,IAAIA,SAAO,gBAAgB;AAI1C,MAAM,UAAwC;CAC7C,SAAS;CACT,SAAS;CACT,MAAM;CACN;AAED,MAAa,OAAO,WACnB,OAAO,IAAI,aAAa;CACvB,MAAM,SAAS,OAAO;CACtB,MAAM,eAAe,OAAO;AAE5B,eAAc,SAAS,gBAAgB,EACtC,YAAY,OAAO,SACnB,CAAC;AAEF,QAAO,KAAK,+CAA+C;CAC3D,MAAM,WAAW,OAAO,UAAU,OAAO,SAAS,OAAO,YAAY;AACrE,QAAO,QAAQ,+BAA+B;AAE9C,QAAO,KAAK,WAAW,aAAa,yBAAyB;CAC7D,MAAM,SAAS,GAAG,QAAQ,cAAc,aAAa,SAAS,KAAK,gBAAgB,SAAS,QAAQ,MAAM,GAAG,IAAK;CAClH,MAAM,SAAS,OAAO,OAAO,gBAAgB,OAAO;AACpD,QAAO,QAAQ,oBAAoB;AAEnC,SAAQ,KAAK;AACb,SAAQ,IAAI,UAAU;AACtB,SAAQ,IAAI,OAAO;AACnB,SAAQ,IAAI,UAAU;AACtB,SAAQ,KAAK;EACZ"}
|
package/lib/commands/describe.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SEPARATOR, displayHeader } from "../utils/args.js";
|
|
2
|
-
import { parseGitHubUrl } from "../utils/validation.js";
|
|
3
2
|
import { Logger as Logger$1 } from "../utils/logger.js";
|
|
3
|
+
import { parseGitHubUrl } from "../utils/validation.js";
|
|
4
4
|
import { Gemini, GitHub, fetchRepo } from "../services.js";
|
|
5
5
|
import { Effect } from "effect";
|
|
6
6
|
//#region src/commands/describe.ts
|
|
@@ -47,7 +47,7 @@ const run = (config) => Effect.gen(function* () {
|
|
|
47
47
|
Repository: config.repoUrl
|
|
48
48
|
});
|
|
49
49
|
logger.info("Fetching repository content via GitIngest...");
|
|
50
|
-
const repoData = yield* fetchRepo(config.repoUrl);
|
|
50
|
+
const repoData = yield* fetchRepo(config.repoUrl, config.githubToken);
|
|
51
51
|
logger.success("Content fetched successfully");
|
|
52
52
|
logger.info("Generating description and detecting homepage via Gemini...");
|
|
53
53
|
const prompt = `${DESCRIBE_PROMPT}\n\nURL: ${repoData.repo_url}\nSummary: ${repoData.summary}\nTree:\n${repoData.tree}\n\nContent:\n${repoData.content.slice(0, 6e3)}`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"describe.js","names":["Logger","validation.parseGitHubUrl"],"sources":["../../src/commands/describe.ts"],"sourcesContent":["/**\n *\n * Copyright 2026 Mike Odnis\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\nimport { Effect } from \"effect\";\n\nimport type { GrepoConfig } from \"../config.js\";\nimport { fetchRepo, Gemini, GitHub } from \"../services.js\";\nimport { displayHeader, SEPARATOR } from \"../utils/args.js\";\nimport { Logger } from \"../utils/logger.js\";\nimport * as validation from \"../utils/validation.js\";\n\nconst logger = new Logger(\"GREPO:DESCRIBE\");\n\nconst DESCRIBE_PROMPT = `Analyze this repository and generate:\n1. A concise repository description (max 350 characters) suitable for GitHub's \"About\" section\n2. A homepage URL if you can detect one from the repository content\n\nLook for homepage URLs in:\n- package.json \"homepage\" field\n- docs site configurations (docusaurus, vitepress, mkdocs, etc.)\n- deployment configs referencing domains (vercel.json, netlify.toml, CNAME files)\n- GitHub Pages configuration\n- README badges or links pointing to live demos, docs, or package registries (npm, PyPI, crates.io, etc.)\n\nReturn ONLY a JSON object with this shape:\n{\n \"description\": \"the description here\",\n \"homepage\": \"https://example.com or null if not found\"\n}`;\n\nexport const run = (config: GrepoConfig) =>\n\tEffect.gen(function* () {\n\t\tconst gemini = yield* Gemini;\n\t\tconst github = yield* GitHub;\n\n\t\tdisplayHeader(\"grepo describe\", {\n\t\t\tApply: config.shouldApply ? \"Yes\" : \"No\",\n\t\t\t\"Dry Run\": config.isDryRun ? \"Yes\" : \"No\",\n\t\t\tRepository: config.repoUrl,\n\t\t});\n\n\t\tlogger.info(\"Fetching repository content via GitIngest...\");\n\t\tconst repoData = yield* fetchRepo(config.repoUrl);\n\t\tlogger.success(\"Content fetched successfully\");\n\n\t\tlogger.info(\"Generating description and detecting homepage via Gemini...\");\n\t\tconst prompt = `${DESCRIBE_PROMPT}\\n\\nURL: ${repoData.repo_url}\\nSummary: ${repoData.summary}\\nTree:\\n${repoData.tree}\\n\\nContent:\\n${repoData.content.slice(0, 6000)}`;\n\t\tconst result = yield* gemini.generateContent(prompt);\n\t\tlogger.success(\"Analysis complete\");\n\n\t\tconst jsonMatch = /\\{[\\s\\S]*?\\}/.exec(result);\n\t\tif (!jsonMatch) {\n\t\t\tlogger.warn(\"Could not parse AI response\");\n\t\t\tconsole.log();\n\t\t\tconsole.log(SEPARATOR);\n\t\t\tconsole.log(result);\n\t\t\tconsole.log(SEPARATOR);\n\t\t\treturn;\n\t\t}\n\n\t\tconst parsed = JSON.parse(jsonMatch[0]) as {\n\t\t\tdescription: string;\n\t\t\thomepage: string | null;\n\t\t};\n\n\t\tconsole.log();\n\t\tconsole.log(SEPARATOR);\n\t\tconsole.log(` Description: ${parsed.description}`);\n\t\tconsole.log(` Homepage: ${parsed.homepage || \"(none detected)\"}`);\n\t\tconsole.log(SEPARATOR);\n\t\tconsole.log();\n\n\t\tif (parsed.description.length > 350) {\n\t\t\tlogger.warn(\n\t\t\t\t`Description is ${parsed.description.length} chars (max 350), it will be truncated by GitHub`,\n\t\t\t);\n\t\t}\n\n\t\tconst { owner, repo } = validation.parseGitHubUrl(config.repoUrl);\n\n\t\tconst updateData: { description?: string; homepage?: string } = {\n\t\t\tdescription: parsed.description,\n\t\t};\n\t\tif (parsed.homepage) {\n\t\t\tupdateData.homepage = parsed.homepage;\n\t\t}\n\n\t\tif (config.isDryRun) {\n\t\t\tlogger.info(\"DRY RUN: Would set:\", updateData);\n\t\t\treturn;\n\t\t}\n\n\t\tif (config.shouldApply) {\n\t\t\tlogger.info(`Updating ${owner}/${repo} on GitHub...`);\n\t\t\tyield* github.updateRepo(owner, repo, updateData);\n\t\t\tlogger.success(\n\t\t\t\t\"Repository description and homepage updated successfully\",\n\t\t\t);\n\t\t} else {\n\t\t\tlogger.info(\"Use --apply to set description and homepage on GitHub\");\n\t\t}\n\t});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA0BA,MAAM,SAAS,IAAIA,SAAO,iBAAiB;AAE3C,MAAM,kBAAkB;;;;;;;;;;;;;;;;AAiBxB,MAAa,OAAO,WACnB,OAAO,IAAI,aAAa;CACvB,MAAM,SAAS,OAAO;CACtB,MAAM,SAAS,OAAO;AAEtB,eAAc,kBAAkB;EAC/B,OAAO,OAAO,cAAc,QAAQ;EACpC,WAAW,OAAO,WAAW,QAAQ;EACrC,YAAY,OAAO;EACnB,CAAC;AAEF,QAAO,KAAK,+CAA+C;CAC3D,MAAM,WAAW,OAAO,UAAU,OAAO,
|
|
1
|
+
{"version":3,"file":"describe.js","names":["Logger","validation.parseGitHubUrl"],"sources":["../../src/commands/describe.ts"],"sourcesContent":["/**\n *\n * Copyright 2026 Mike Odnis\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\nimport { Effect } from \"effect\";\n\nimport type { GrepoConfig } from \"../config.js\";\nimport { fetchRepo, Gemini, GitHub } from \"../services.js\";\nimport { displayHeader, SEPARATOR } from \"../utils/args.js\";\nimport { Logger } from \"../utils/logger.js\";\nimport * as validation from \"../utils/validation.js\";\n\nconst logger = new Logger(\"GREPO:DESCRIBE\");\n\nconst DESCRIBE_PROMPT = `Analyze this repository and generate:\n1. A concise repository description (max 350 characters) suitable for GitHub's \"About\" section\n2. A homepage URL if you can detect one from the repository content\n\nLook for homepage URLs in:\n- package.json \"homepage\" field\n- docs site configurations (docusaurus, vitepress, mkdocs, etc.)\n- deployment configs referencing domains (vercel.json, netlify.toml, CNAME files)\n- GitHub Pages configuration\n- README badges or links pointing to live demos, docs, or package registries (npm, PyPI, crates.io, etc.)\n\nReturn ONLY a JSON object with this shape:\n{\n \"description\": \"the description here\",\n \"homepage\": \"https://example.com or null if not found\"\n}`;\n\nexport const run = (config: GrepoConfig) =>\n\tEffect.gen(function* () {\n\t\tconst gemini = yield* Gemini;\n\t\tconst github = yield* GitHub;\n\n\t\tdisplayHeader(\"grepo describe\", {\n\t\t\tApply: config.shouldApply ? \"Yes\" : \"No\",\n\t\t\t\"Dry Run\": config.isDryRun ? \"Yes\" : \"No\",\n\t\t\tRepository: config.repoUrl,\n\t\t});\n\n\t\tlogger.info(\"Fetching repository content via GitIngest...\");\n\t\tconst repoData = yield* fetchRepo(config.repoUrl, config.githubToken);\n\t\tlogger.success(\"Content fetched successfully\");\n\n\t\tlogger.info(\"Generating description and detecting homepage via Gemini...\");\n\t\tconst prompt = `${DESCRIBE_PROMPT}\\n\\nURL: ${repoData.repo_url}\\nSummary: ${repoData.summary}\\nTree:\\n${repoData.tree}\\n\\nContent:\\n${repoData.content.slice(0, 6000)}`;\n\t\tconst result = yield* gemini.generateContent(prompt);\n\t\tlogger.success(\"Analysis complete\");\n\n\t\tconst jsonMatch = /\\{[\\s\\S]*?\\}/.exec(result);\n\t\tif (!jsonMatch) {\n\t\t\tlogger.warn(\"Could not parse AI response\");\n\t\t\tconsole.log();\n\t\t\tconsole.log(SEPARATOR);\n\t\t\tconsole.log(result);\n\t\t\tconsole.log(SEPARATOR);\n\t\t\treturn;\n\t\t}\n\n\t\tconst parsed = JSON.parse(jsonMatch[0]) as {\n\t\t\tdescription: string;\n\t\t\thomepage: string | null;\n\t\t};\n\n\t\tconsole.log();\n\t\tconsole.log(SEPARATOR);\n\t\tconsole.log(` Description: ${parsed.description}`);\n\t\tconsole.log(` Homepage: ${parsed.homepage || \"(none detected)\"}`);\n\t\tconsole.log(SEPARATOR);\n\t\tconsole.log();\n\n\t\tif (parsed.description.length > 350) {\n\t\t\tlogger.warn(\n\t\t\t\t`Description is ${parsed.description.length} chars (max 350), it will be truncated by GitHub`,\n\t\t\t);\n\t\t}\n\n\t\tconst { owner, repo } = validation.parseGitHubUrl(config.repoUrl);\n\n\t\tconst updateData: { description?: string; homepage?: string } = {\n\t\t\tdescription: parsed.description,\n\t\t};\n\t\tif (parsed.homepage) {\n\t\t\tupdateData.homepage = parsed.homepage;\n\t\t}\n\n\t\tif (config.isDryRun) {\n\t\t\tlogger.info(\"DRY RUN: Would set:\", updateData);\n\t\t\treturn;\n\t\t}\n\n\t\tif (config.shouldApply) {\n\t\t\tlogger.info(`Updating ${owner}/${repo} on GitHub...`);\n\t\t\tyield* github.updateRepo(owner, repo, updateData);\n\t\t\tlogger.success(\n\t\t\t\t\"Repository description and homepage updated successfully\",\n\t\t\t);\n\t\t} else {\n\t\t\tlogger.info(\"Use --apply to set description and homepage on GitHub\");\n\t\t}\n\t});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA0BA,MAAM,SAAS,IAAIA,SAAO,iBAAiB;AAE3C,MAAM,kBAAkB;;;;;;;;;;;;;;;;AAiBxB,MAAa,OAAO,WACnB,OAAO,IAAI,aAAa;CACvB,MAAM,SAAS,OAAO;CACtB,MAAM,SAAS,OAAO;AAEtB,eAAc,kBAAkB;EAC/B,OAAO,OAAO,cAAc,QAAQ;EACpC,WAAW,OAAO,WAAW,QAAQ;EACrC,YAAY,OAAO;EACnB,CAAC;AAEF,QAAO,KAAK,+CAA+C;CAC3D,MAAM,WAAW,OAAO,UAAU,OAAO,SAAS,OAAO,YAAY;AACrE,QAAO,QAAQ,+BAA+B;AAE9C,QAAO,KAAK,8DAA8D;CAC1E,MAAM,SAAS,GAAG,gBAAgB,WAAW,SAAS,SAAS,aAAa,SAAS,QAAQ,WAAW,SAAS,KAAK,gBAAgB,SAAS,QAAQ,MAAM,GAAG,IAAK;CACrK,MAAM,SAAS,OAAO,OAAO,gBAAgB,OAAO;AACpD,QAAO,QAAQ,oBAAoB;CAEnC,MAAM,YAAY,eAAe,KAAK,OAAO;AAC7C,KAAI,CAAC,WAAW;AACf,SAAO,KAAK,8BAA8B;AAC1C,UAAQ,KAAK;AACb,UAAQ,IAAI,UAAU;AACtB,UAAQ,IAAI,OAAO;AACnB,UAAQ,IAAI,UAAU;AACtB;;CAGD,MAAM,SAAS,KAAK,MAAM,UAAU,GAAG;AAKvC,SAAQ,KAAK;AACb,SAAQ,IAAI,UAAU;AACtB,SAAQ,IAAI,kBAAkB,OAAO,cAAc;AACnD,SAAQ,IAAI,kBAAkB,OAAO,YAAY,oBAAoB;AACrE,SAAQ,IAAI,UAAU;AACtB,SAAQ,KAAK;AAEb,KAAI,OAAO,YAAY,SAAS,IAC/B,QAAO,KACN,kBAAkB,OAAO,YAAY,OAAO,kDAC5C;CAGF,MAAM,EAAE,OAAO,SAASC,eAA0B,OAAO,QAAQ;CAEjE,MAAM,aAA0D,EAC/D,aAAa,OAAO,aACpB;AACD,KAAI,OAAO,SACV,YAAW,WAAW,OAAO;AAG9B,KAAI,OAAO,UAAU;AACpB,SAAO,KAAK,uBAAuB,WAAW;AAC9C;;AAGD,KAAI,OAAO,aAAa;AACvB,SAAO,KAAK,YAAY,MAAM,GAAG,KAAK,eAAe;AACrD,SAAO,OAAO,WAAW,OAAO,MAAM,WAAW;AACjD,SAAO,QACN,2DACA;OAED,QAAO,KAAK,wDAAwD;EAEpE"}
|
package/lib/commands/readme.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { displayHeader } from "../utils/args.js";
|
|
2
|
-
import { parseGitHubUrl } from "../utils/validation.js";
|
|
3
2
|
import { Logger as Logger$1 } from "../utils/logger.js";
|
|
3
|
+
import { parseGitHubUrl } from "../utils/validation.js";
|
|
4
4
|
import { Gemini, GitHub, fetchRepo } from "../services.js";
|
|
5
5
|
import { validateAndFixMermaid } from "../mermaid.js";
|
|
6
6
|
import { buildAnalysisPrompt, buildGenerationPrompt, extractExistingReadme, parseAnalysis } from "../prompts/readme.js";
|
|
@@ -36,7 +36,7 @@ const run = (config) => Effect.gen(function* () {
|
|
|
36
36
|
Style: config.style
|
|
37
37
|
});
|
|
38
38
|
logger.info("Fetching repository content via GitIngest...");
|
|
39
|
-
const repoData = yield* fetchRepo(config.repoUrl);
|
|
39
|
+
const repoData = yield* fetchRepo(config.repoUrl, config.githubToken);
|
|
40
40
|
logger.success("Content fetched successfully");
|
|
41
41
|
logger.info("Analyzing repository structure...");
|
|
42
42
|
const analysisPrompt = buildAnalysisPrompt(repoData, extractExistingReadme(repoData.content) ?? void 0);
|
|
@@ -59,7 +59,7 @@ const run = (config) => Effect.gen(function* () {
|
|
|
59
59
|
if (config.shouldPush) {
|
|
60
60
|
const { owner, repo } = parseGitHubUrl(config.repoUrl);
|
|
61
61
|
logger.info(`Pushing README to GitHub (${owner}/${repo})...`);
|
|
62
|
-
yield* github.pushFile(owner, repo, config.outputFile, content, "docs: update README with AI-generated content", config.branch);
|
|
62
|
+
yield* github.pushFile(owner, repo, config.outputFile, content, "docs: update README with AI-generated content", config.branch ?? "main");
|
|
63
63
|
logger.success("Pushed to GitHub successfully");
|
|
64
64
|
}
|
|
65
65
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"readme.js","names":["Logger","validation.parseGitHubUrl"],"sources":["../../src/commands/readme.ts"],"sourcesContent":["/**\n *\n * Copyright 2026 Mike Odnis\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\nimport { writeFile } from \"node:fs/promises\";\nimport { Effect } from \"effect\";\n\nimport type { GrepoConfig } from \"../config.js\";\nimport { validateAndFixMermaid } from \"../mermaid.js\";\nimport {\n\tbuildAnalysisPrompt,\n\tbuildGenerationPrompt,\n\textractExistingReadme,\n\tparseAnalysis,\n} from \"../prompts/readme.js\";\nimport { fetchRepo, Gemini, GitHub } from \"../services.js\";\nimport { displayHeader } from \"../utils/args.js\";\nimport { Logger } from \"../utils/logger.js\";\nimport * as validation from \"../utils/validation.js\";\n\nconst logger = new Logger(\"GREPO:README\");\n\nexport const run = (config: GrepoConfig) =>\n\tEffect.gen(function* () {\n\t\tconst gemini = yield* Gemini;\n\t\tconst github = yield* GitHub;\n\n\t\tdisplayHeader(\"grepo readme\", {\n\t\t\tFormat: config.outputFormat,\n\t\t\tOutput: config.outputFile,\n\t\t\tPush: config.shouldPush ? `Yes (${config.branch})` : \"No\",\n\t\t\tRepository: config.repoUrl,\n\t\t\tStyle: config.style,\n\t\t});\n\n\t\tlogger.info(\"Fetching repository content via GitIngest...\");\n\t\tconst repoData = yield* fetchRepo(config.repoUrl);\n\t\tlogger.success(\"Content fetched successfully\");\n\n\t\t// Phase 1: Analysis\n\t\tlogger.info(\"Analyzing repository structure...\");\n\t\tconst existingReadme = extractExistingReadme(repoData.content);\n\t\tconst analysisPrompt = buildAnalysisPrompt(\n\t\t\trepoData,\n\t\t\texistingReadme ?? undefined,\n\t\t);\n\t\tconst analysisRaw = yield* gemini.generateContent(analysisPrompt);\n\t\tconst analysis = parseAnalysis(analysisRaw);\n\t\tlogger.success(\"Analysis complete\");\n\n\t\t// Phase 2: Generation\n\t\tlogger.info(\"Generating README content via Gemini...\");\n\t\tconst generationPrompt = buildGenerationPrompt(analysis, repoData, {\n\t\t\tformat: config.outputFormat,\n\t\t\tstyle: config.style,\n\t\t\ttone: config.tone,\n\t\t});\n\t\tconst rawContent = yield* gemini.generateContent(generationPrompt);\n\t\tlogger.success(\"README generated successfully\");\n\n\t\t// Phase 3: Mermaid validation\n\t\tconst content = yield* validateAndFixMermaid(rawContent, gemini);\n\n\t\tif (config.outputFile) {\n\t\t\tlogger.info(`Saving README to ${config.outputFile}...`);\n\t\t\tyield* Effect.promise(() => writeFile(config.outputFile, content));\n\t\t\tlogger.success(\"File saved locally\");\n\t\t}\n\n\t\tif (config.shouldPush) {\n\t\t\tconst { owner, repo } = validation.parseGitHubUrl(config.repoUrl);\n\t\t\tlogger.info(`Pushing README to GitHub (${owner}/${repo})...`);\n\t\t\tyield* github.pushFile(\n\t\t\t\towner,\n\t\t\t\trepo,\n\t\t\t\tconfig.outputFile,\n\t\t\t\tcontent,\n\t\t\t\t\"docs: update README with AI-generated content\",\n\t\t\t\tconfig.branch,\n\t\t\t);\n\t\t\tlogger.success(\"Pushed to GitHub successfully\");\n\t\t}\n\t});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,MAAM,SAAS,IAAIA,SAAO,eAAe;AAEzC,MAAa,OAAO,WACnB,OAAO,IAAI,aAAa;CACvB,MAAM,SAAS,OAAO;CACtB,MAAM,SAAS,OAAO;AAEtB,eAAc,gBAAgB;EAC7B,QAAQ,OAAO;EACf,QAAQ,OAAO;EACf,MAAM,OAAO,aAAa,QAAQ,OAAO,OAAO,KAAK;EACrD,YAAY,OAAO;EACnB,OAAO,OAAO;EACd,CAAC;AAEF,QAAO,KAAK,+CAA+C;CAC3D,MAAM,WAAW,OAAO,UAAU,OAAO,
|
|
1
|
+
{"version":3,"file":"readme.js","names":["Logger","validation.parseGitHubUrl"],"sources":["../../src/commands/readme.ts"],"sourcesContent":["/**\n *\n * Copyright 2026 Mike Odnis\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\nimport { writeFile } from \"node:fs/promises\";\nimport { Effect } from \"effect\";\n\nimport type { GrepoConfig } from \"../config.js\";\nimport { validateAndFixMermaid } from \"../mermaid.js\";\nimport {\n\tbuildAnalysisPrompt,\n\tbuildGenerationPrompt,\n\textractExistingReadme,\n\tparseAnalysis,\n} from \"../prompts/readme.js\";\nimport { fetchRepo, Gemini, GitHub } from \"../services.js\";\nimport { displayHeader } from \"../utils/args.js\";\nimport { Logger } from \"../utils/logger.js\";\nimport * as validation from \"../utils/validation.js\";\n\nconst logger = new Logger(\"GREPO:README\");\n\nexport const run = (config: GrepoConfig) =>\n\tEffect.gen(function* () {\n\t\tconst gemini = yield* Gemini;\n\t\tconst github = yield* GitHub;\n\n\t\tdisplayHeader(\"grepo readme\", {\n\t\t\tFormat: config.outputFormat,\n\t\t\tOutput: config.outputFile,\n\t\t\tPush: config.shouldPush ? `Yes (${config.branch})` : \"No\",\n\t\t\tRepository: config.repoUrl,\n\t\t\tStyle: config.style,\n\t\t});\n\n\t\tlogger.info(\"Fetching repository content via GitIngest...\");\n\t\tconst repoData = yield* fetchRepo(config.repoUrl, config.githubToken);\n\t\tlogger.success(\"Content fetched successfully\");\n\n\t\t// Phase 1: Analysis\n\t\tlogger.info(\"Analyzing repository structure...\");\n\t\tconst existingReadme = extractExistingReadme(repoData.content);\n\t\tconst analysisPrompt = buildAnalysisPrompt(\n\t\t\trepoData,\n\t\t\texistingReadme ?? undefined,\n\t\t);\n\t\tconst analysisRaw = yield* gemini.generateContent(analysisPrompt);\n\t\tconst analysis = parseAnalysis(analysisRaw);\n\t\tlogger.success(\"Analysis complete\");\n\n\t\t// Phase 2: Generation\n\t\tlogger.info(\"Generating README content via Gemini...\");\n\t\tconst generationPrompt = buildGenerationPrompt(analysis, repoData, {\n\t\t\tformat: config.outputFormat,\n\t\t\tstyle: config.style,\n\t\t\ttone: config.tone,\n\t\t});\n\t\tconst rawContent = yield* gemini.generateContent(generationPrompt);\n\t\tlogger.success(\"README generated successfully\");\n\n\t\t// Phase 3: Mermaid validation\n\t\tconst content = yield* validateAndFixMermaid(rawContent, gemini);\n\n\t\tif (config.outputFile) {\n\t\t\tlogger.info(`Saving README to ${config.outputFile}...`);\n\t\t\tyield* Effect.promise(() => writeFile(config.outputFile, content));\n\t\t\tlogger.success(\"File saved locally\");\n\t\t}\n\n\t\tif (config.shouldPush) {\n\t\t\tconst { owner, repo } = validation.parseGitHubUrl(config.repoUrl);\n\t\t\tlogger.info(`Pushing README to GitHub (${owner}/${repo})...`);\n\t\t\tyield* github.pushFile(\n\t\t\t\towner,\n\t\t\t\trepo,\n\t\t\t\tconfig.outputFile,\n\t\t\t\tcontent,\n\t\t\t\t\"docs: update README with AI-generated content\",\n\t\t\t\tconfig.branch ?? \"main\",\n\t\t\t);\n\t\t\tlogger.success(\"Pushed to GitHub successfully\");\n\t\t}\n\t});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,MAAM,SAAS,IAAIA,SAAO,eAAe;AAEzC,MAAa,OAAO,WACnB,OAAO,IAAI,aAAa;CACvB,MAAM,SAAS,OAAO;CACtB,MAAM,SAAS,OAAO;AAEtB,eAAc,gBAAgB;EAC7B,QAAQ,OAAO;EACf,QAAQ,OAAO;EACf,MAAM,OAAO,aAAa,QAAQ,OAAO,OAAO,KAAK;EACrD,YAAY,OAAO;EACnB,OAAO,OAAO;EACd,CAAC;AAEF,QAAO,KAAK,+CAA+C;CAC3D,MAAM,WAAW,OAAO,UAAU,OAAO,SAAS,OAAO,YAAY;AACrE,QAAO,QAAQ,+BAA+B;AAG9C,QAAO,KAAK,oCAAoC;CAEhD,MAAM,iBAAiB,oBACtB,UAFsB,sBAAsB,SAAS,QAAQ,IAG3C,KAAA,EAClB;CAED,MAAM,WAAW,cADG,OAAO,OAAO,gBAAgB,eAAe,CACtB;AAC3C,QAAO,QAAQ,oBAAoB;AAGnC,QAAO,KAAK,0CAA0C;CACtD,MAAM,mBAAmB,sBAAsB,UAAU,UAAU;EAClE,QAAQ,OAAO;EACf,OAAO,OAAO;EACd,MAAM,OAAO;EACb,CAAC;CACF,MAAM,aAAa,OAAO,OAAO,gBAAgB,iBAAiB;AAClE,QAAO,QAAQ,gCAAgC;CAG/C,MAAM,UAAU,OAAO,sBAAsB,YAAY,OAAO;AAEhE,KAAI,OAAO,YAAY;AACtB,SAAO,KAAK,oBAAoB,OAAO,WAAW,KAAK;AACvD,SAAO,OAAO,cAAc,UAAU,OAAO,YAAY,QAAQ,CAAC;AAClE,SAAO,QAAQ,qBAAqB;;AAGrC,KAAI,OAAO,YAAY;EACtB,MAAM,EAAE,OAAO,SAASC,eAA0B,OAAO,QAAQ;AACjE,SAAO,KAAK,6BAA6B,MAAM,GAAG,KAAK,MAAM;AAC7D,SAAO,OAAO,SACb,OACA,MACA,OAAO,YACP,SACA,iDACA,OAAO,UAAU,OACjB;AACD,SAAO,QAAQ,gCAAgC;;EAE/C"}
|
package/lib/commands/topics.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SEPARATOR, displayHeader } from "../utils/args.js";
|
|
2
|
-
import { parseGitHubUrl } from "../utils/validation.js";
|
|
3
2
|
import { Logger as Logger$1 } from "../utils/logger.js";
|
|
3
|
+
import { parseGitHubUrl } from "../utils/validation.js";
|
|
4
4
|
import { Gemini, GitHub, fetchRepo } from "../services.js";
|
|
5
5
|
import { Effect } from "effect";
|
|
6
6
|
//#region src/commands/topics.ts
|
|
@@ -34,7 +34,7 @@ const run = (config) => Effect.gen(function* () {
|
|
|
34
34
|
Repository: config.repoUrl
|
|
35
35
|
});
|
|
36
36
|
logger.info("Fetching repository content via GitIngest...");
|
|
37
|
-
const repoData = yield* fetchRepo(config.repoUrl);
|
|
37
|
+
const repoData = yield* fetchRepo(config.repoUrl, config.githubToken);
|
|
38
38
|
logger.success("Content fetched successfully");
|
|
39
39
|
logger.info("Running topics analysis via Gemini...");
|
|
40
40
|
const prompt = `${TOPICS_PROMPT}\n\nTree:\n${repoData.tree}\n\nContent:\n${repoData.content.slice(0, 4e3)}`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"topics.js","names":["Logger","validation.parseGitHubUrl"],"sources":["../../src/commands/topics.ts"],"sourcesContent":["/**\n *\n * Copyright 2026 Mike Odnis\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\nimport { Effect } from \"effect\";\n\nimport type { GrepoConfig } from \"../config.js\";\nimport { fetchRepo, Gemini, GitHub } from \"../services.js\";\nimport { displayHeader, SEPARATOR } from \"../utils/args.js\";\nimport { Logger } from \"../utils/logger.js\";\nimport * as validation from \"../utils/validation.js\";\n\nconst logger = new Logger(\"GREPO:TOPICS\");\n\nconst TOPICS_PROMPT = `Analyze this repository and suggest 5-8 relevant GitHub topics.\nReturn ONLY a JSON array of lowercase, hyphenated strings. Example: [\"typescript\", \"cli-tool\", \"ai-powered\"]`;\n\nexport const run = (config: GrepoConfig) =>\n\tEffect.gen(function* () {\n\t\tconst gemini = yield* Gemini;\n\t\tconst github = yield* GitHub;\n\n\t\tdisplayHeader(\"grepo topics\", {\n\t\t\tApply: config.shouldApply ? \"Yes\" : \"No\",\n\t\t\t\"Dry Run\": config.isDryRun ? \"Yes\" : \"No\",\n\t\t\tMerge: config.shouldMerge ? \"Yes\" : \"No\",\n\t\t\tRepository: config.repoUrl,\n\t\t});\n\n\t\tlogger.info(\"Fetching repository content via GitIngest...\");\n\t\tconst repoData = yield* fetchRepo(config.repoUrl);\n\t\tlogger.success(\"Content fetched successfully\");\n\n\t\tlogger.info(\"Running topics analysis via Gemini...\");\n\t\tconst prompt = `${TOPICS_PROMPT}\\n\\nTree:\\n${repoData.tree}\\n\\nContent:\\n${repoData.content.slice(0, 4000)}`;\n\t\tconst result = yield* gemini.generateContent(prompt);\n\t\tlogger.success(\"Analysis complete\");\n\n\t\tconsole.log();\n\t\tconsole.log(SEPARATOR);\n\t\tconsole.log(result);\n\t\tconsole.log(SEPARATOR);\n\t\tconsole.log();\n\n\t\tconst jsonMatch = /\\[[\\s\\S]*?\\]/.exec(result);\n\t\tif (!jsonMatch) {\n\t\t\tlogger.warn(\"Could not find topics list in AI response\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst suggested = (JSON.parse(jsonMatch[0]) as string[]).map((t) =>\n\t\t\tt.toLowerCase().trim().replaceAll(/\\s+/g, \"-\"),\n\t\t);\n\n\t\tconst { owner, repo } = validation.parseGitHubUrl(config.repoUrl);\n\n\t\tlet finalTopics = suggested;\n\t\tif (config.shouldMerge && config.githubToken) {\n\t\t\tlogger.info(\"Fetching current topics from GitHub...\");\n\t\t\tconst current = yield* github.getTopics(owner, repo);\n\t\t\tfinalTopics = [...new Set([...current, ...suggested])].sort((a, b) =>\n\t\t\t\ta.localeCompare(b),\n\t\t\t);\n\t\t}\n\n\t\tif (config.isDryRun) {\n\t\t\tlogger.info(\"DRY RUN: Would apply these topics:\", {\n\t\t\t\ttopics: finalTopics,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tif (config.shouldApply) {\n\t\t\tlogger.info(\"Applying topics to GitHub...\");\n\t\t\tyield* github.setTopics(owner, repo, finalTopics);\n\t\t\tlogger.success(\"Topics applied successfully\");\n\t\t} else {\n\t\t\tlogger.info(\"Use --apply to set these topics on GitHub\");\n\t\t}\n\t});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA0BA,MAAM,SAAS,IAAIA,SAAO,eAAe;AAEzC,MAAM,gBAAgB;;AAGtB,MAAa,OAAO,WACnB,OAAO,IAAI,aAAa;CACvB,MAAM,SAAS,OAAO;CACtB,MAAM,SAAS,OAAO;AAEtB,eAAc,gBAAgB;EAC7B,OAAO,OAAO,cAAc,QAAQ;EACpC,WAAW,OAAO,WAAW,QAAQ;EACrC,OAAO,OAAO,cAAc,QAAQ;EACpC,YAAY,OAAO;EACnB,CAAC;AAEF,QAAO,KAAK,+CAA+C;CAC3D,MAAM,WAAW,OAAO,UAAU,OAAO,
|
|
1
|
+
{"version":3,"file":"topics.js","names":["Logger","validation.parseGitHubUrl"],"sources":["../../src/commands/topics.ts"],"sourcesContent":["/**\n *\n * Copyright 2026 Mike Odnis\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\nimport { Effect } from \"effect\";\n\nimport type { GrepoConfig } from \"../config.js\";\nimport { fetchRepo, Gemini, GitHub } from \"../services.js\";\nimport { displayHeader, SEPARATOR } from \"../utils/args.js\";\nimport { Logger } from \"../utils/logger.js\";\nimport * as validation from \"../utils/validation.js\";\n\nconst logger = new Logger(\"GREPO:TOPICS\");\n\nconst TOPICS_PROMPT = `Analyze this repository and suggest 5-8 relevant GitHub topics.\nReturn ONLY a JSON array of lowercase, hyphenated strings. Example: [\"typescript\", \"cli-tool\", \"ai-powered\"]`;\n\nexport const run = (config: GrepoConfig) =>\n\tEffect.gen(function* () {\n\t\tconst gemini = yield* Gemini;\n\t\tconst github = yield* GitHub;\n\n\t\tdisplayHeader(\"grepo topics\", {\n\t\t\tApply: config.shouldApply ? \"Yes\" : \"No\",\n\t\t\t\"Dry Run\": config.isDryRun ? \"Yes\" : \"No\",\n\t\t\tMerge: config.shouldMerge ? \"Yes\" : \"No\",\n\t\t\tRepository: config.repoUrl,\n\t\t});\n\n\t\tlogger.info(\"Fetching repository content via GitIngest...\");\n\t\tconst repoData = yield* fetchRepo(config.repoUrl, config.githubToken);\n\t\tlogger.success(\"Content fetched successfully\");\n\n\t\tlogger.info(\"Running topics analysis via Gemini...\");\n\t\tconst prompt = `${TOPICS_PROMPT}\\n\\nTree:\\n${repoData.tree}\\n\\nContent:\\n${repoData.content.slice(0, 4000)}`;\n\t\tconst result = yield* gemini.generateContent(prompt);\n\t\tlogger.success(\"Analysis complete\");\n\n\t\tconsole.log();\n\t\tconsole.log(SEPARATOR);\n\t\tconsole.log(result);\n\t\tconsole.log(SEPARATOR);\n\t\tconsole.log();\n\n\t\tconst jsonMatch = /\\[[\\s\\S]*?\\]/.exec(result);\n\t\tif (!jsonMatch) {\n\t\t\tlogger.warn(\"Could not find topics list in AI response\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst suggested = (JSON.parse(jsonMatch[0]) as string[]).map((t) =>\n\t\t\tt.toLowerCase().trim().replaceAll(/\\s+/g, \"-\"),\n\t\t);\n\n\t\tconst { owner, repo } = validation.parseGitHubUrl(config.repoUrl);\n\n\t\tlet finalTopics = suggested;\n\t\tif (config.shouldMerge && config.githubToken) {\n\t\t\tlogger.info(\"Fetching current topics from GitHub...\");\n\t\t\tconst current = yield* github.getTopics(owner, repo);\n\t\t\tfinalTopics = [...new Set([...current, ...suggested])].sort((a, b) =>\n\t\t\t\ta.localeCompare(b),\n\t\t\t);\n\t\t}\n\n\t\tif (config.isDryRun) {\n\t\t\tlogger.info(\"DRY RUN: Would apply these topics:\", {\n\t\t\t\ttopics: finalTopics,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tif (config.shouldApply) {\n\t\t\tlogger.info(\"Applying topics to GitHub...\");\n\t\t\tyield* github.setTopics(owner, repo, finalTopics);\n\t\t\tlogger.success(\"Topics applied successfully\");\n\t\t} else {\n\t\t\tlogger.info(\"Use --apply to set these topics on GitHub\");\n\t\t}\n\t});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA0BA,MAAM,SAAS,IAAIA,SAAO,eAAe;AAEzC,MAAM,gBAAgB;;AAGtB,MAAa,OAAO,WACnB,OAAO,IAAI,aAAa;CACvB,MAAM,SAAS,OAAO;CACtB,MAAM,SAAS,OAAO;AAEtB,eAAc,gBAAgB;EAC7B,OAAO,OAAO,cAAc,QAAQ;EACpC,WAAW,OAAO,WAAW,QAAQ;EACrC,OAAO,OAAO,cAAc,QAAQ;EACpC,YAAY,OAAO;EACnB,CAAC;AAEF,QAAO,KAAK,+CAA+C;CAC3D,MAAM,WAAW,OAAO,UAAU,OAAO,SAAS,OAAO,YAAY;AACrE,QAAO,QAAQ,+BAA+B;AAE9C,QAAO,KAAK,wCAAwC;CACpD,MAAM,SAAS,GAAG,cAAc,aAAa,SAAS,KAAK,gBAAgB,SAAS,QAAQ,MAAM,GAAG,IAAK;CAC1G,MAAM,SAAS,OAAO,OAAO,gBAAgB,OAAO;AACpD,QAAO,QAAQ,oBAAoB;AAEnC,SAAQ,KAAK;AACb,SAAQ,IAAI,UAAU;AACtB,SAAQ,IAAI,OAAO;AACnB,SAAQ,IAAI,UAAU;AACtB,SAAQ,KAAK;CAEb,MAAM,YAAY,eAAe,KAAK,OAAO;AAC7C,KAAI,CAAC,WAAW;AACf,SAAO,KAAK,4CAA4C;AACxD;;CAGD,MAAM,YAAa,KAAK,MAAM,UAAU,GAAG,CAAc,KAAK,MAC7D,EAAE,aAAa,CAAC,MAAM,CAAC,WAAW,QAAQ,IAAI,CAC9C;CAED,MAAM,EAAE,OAAO,SAASC,eAA0B,OAAO,QAAQ;CAEjE,IAAI,cAAc;AAClB,KAAI,OAAO,eAAe,OAAO,aAAa;AAC7C,SAAO,KAAK,yCAAyC;EACrD,MAAM,UAAU,OAAO,OAAO,UAAU,OAAO,KAAK;AACpD,gBAAc,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,SAAS,GAAG,UAAU,CAAC,CAAC,CAAC,MAAM,GAAG,MAC/D,EAAE,cAAc,EAAE,CAClB;;AAGF,KAAI,OAAO,UAAU;AACpB,SAAO,KAAK,sCAAsC,EACjD,QAAQ,aACR,CAAC;AACF;;AAGD,KAAI,OAAO,aAAa;AACvB,SAAO,KAAK,+BAA+B;AAC3C,SAAO,OAAO,UAAU,OAAO,MAAM,YAAY;AACjD,SAAO,QAAQ,8BAA8B;OAE7C,QAAO,KAAK,4CAA4C;EAExD"}
|
package/lib/config.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ type OutputFormat = Schema.Schema.Type<typeof OutputFormat>;
|
|
|
9
9
|
declare const DocumentationStyle: Schema.Literal<["minimal", "standard", "comprehensive"]>;
|
|
10
10
|
type DocumentationStyle = Schema.Schema.Type<typeof DocumentationStyle>;
|
|
11
11
|
declare const GrepoConfig: Schema.Struct<{
|
|
12
|
-
branch: typeof Schema.String
|
|
12
|
+
branch: Schema.optional<typeof Schema.String>;
|
|
13
13
|
command: Schema.Literal<["readme", "topics", "describe", "summary", "tech", "improve"]>;
|
|
14
14
|
geminiApiKey: typeof Schema.String;
|
|
15
15
|
githubToken: Schema.optional<typeof Schema.String>;
|
package/lib/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","names":[],"sources":["../src/config.ts"],"sourcesContent":[],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"config.d.ts","names":[],"sources":["../src/config.ts"],"sourcesContent":[],"mappings":";;;;cAgCa,SAAO,MAAA,CAAA;KAQR,OAAA,GAAU,MAAA,CAAO,MAAA,CAAO,YAAY;cAEnC,cAAY,MAAA,CAAA;KACb,YAAA,GAAe,MAAA,CAAO,MAAA,CAAO,YAAY;cAExC,oBAAkB,MAAA,CAAA;KAKnB,kBAAA,GAAqB,MAAA,CAAO,MAAA,CAAO,YAAY;cAE9C,aAAW,MAAA,CAAA;;EAAA,OAAA,gBAAA,CAAA,CAAA,QAAA,EAAA,QAAA,EAAA,UAAA,EAAA,SAAA,EAAA,MAAA,EAAA,SAAA,CAAA,CAAA;EAAA,YAAA,EAAA,oBAAA;EAiBZ,WAAA,iBAAwC,CAAA,oBAAnB,CAAA;EAMX,QAAA,EAAO,qBAAW;EAsExB,UAAA,EAAA,oBAA6B;;;;;;;;;KA5EjC,WAAA,GAAc,MAAA,CAAO,MAAA,CAAO,YAAY;iBAM9B,OAAA,CAAA,GAAW;iBAsEjB,WAAA,kBAA6B"}
|
package/lib/config.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { GrepoValidationError } from "./errors.js";
|
|
2
2
|
import { parseArgs } from "./utils/args.js";
|
|
3
|
+
import { loadConfigFile } from "./utils/config-file.js";
|
|
3
4
|
import { isValidGeminiApiKey, isValidGitHubUrl } from "./utils/validation.js";
|
|
4
5
|
import { Schema } from "effect";
|
|
5
6
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -27,7 +28,7 @@ const Command = Schema.Literal("readme", "topics", "describe", "summary", "tech"
|
|
|
27
28
|
const OutputFormat = Schema.Literal("md", "mdx");
|
|
28
29
|
const DocumentationStyle = Schema.Literal("minimal", "standard", "comprehensive");
|
|
29
30
|
const GrepoConfig = Schema.Struct({
|
|
30
|
-
branch: Schema.String,
|
|
31
|
+
branch: Schema.optional(Schema.String),
|
|
31
32
|
command: Command,
|
|
32
33
|
geminiApiKey: Schema.String,
|
|
33
34
|
githubToken: Schema.optional(Schema.String),
|
|
@@ -79,7 +80,7 @@ Options:
|
|
|
79
80
|
--apply Apply changes to GitHub (topics, describe)
|
|
80
81
|
--merge Merge with existing topics (topics only)
|
|
81
82
|
--dry-run Preview changes without applying
|
|
82
|
-
--branch <name> Target branch (default:
|
|
83
|
+
--branch <name> Target branch (default: auto-detect from repo)
|
|
83
84
|
--tone <voice> Tone: casual, professional, minimal, technical (default: auto-detect)`;
|
|
84
85
|
const VALID_TONES = [
|
|
85
86
|
"casual",
|
|
@@ -119,16 +120,17 @@ function buildConfig(argv) {
|
|
|
119
120
|
field: "repoUrl",
|
|
120
121
|
message: "Invalid GitHub URL"
|
|
121
122
|
});
|
|
122
|
-
const
|
|
123
|
+
const configFile = loadConfigFile();
|
|
124
|
+
const geminiApiKey = process.env.GEMINI_API_KEY ?? process.env.GOOGLE_API_KEY ?? configFile.geminiApiKey ?? "";
|
|
123
125
|
if (!isValidGeminiApiKey(geminiApiKey)) throw new GrepoValidationError({ message: "GEMINI_API_KEY is missing or invalid" });
|
|
124
|
-
const githubToken = process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN;
|
|
126
|
+
const githubToken = process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN ?? configFile.githubToken;
|
|
125
127
|
const shouldApply = !!options.apply;
|
|
126
128
|
const shouldPush = !!options.push;
|
|
127
129
|
const shouldMerge = !!options.merge;
|
|
128
130
|
if ((shouldApply || shouldPush || shouldMerge) && !githubToken) throw new GrepoValidationError({ message: "GitHub token (GITHUB_TOKEN or GH_TOKEN) is required for mutation operations" });
|
|
129
131
|
const format = options.format || "md";
|
|
130
132
|
return {
|
|
131
|
-
branch: options.branch ||
|
|
133
|
+
branch: options.branch || void 0,
|
|
132
134
|
command,
|
|
133
135
|
geminiApiKey,
|
|
134
136
|
githubToken,
|
package/lib/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","names":["validation.isValidGitHubUrl","validation.isValidGeminiApiKey"],"sources":["../src/config.ts"],"sourcesContent":["/**\n *\n * Copyright 2026 Mike Odnis\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { dirname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { Schema } from \"effect\";\n\nimport { GrepoValidationError } from \"./errors.js\";\nimport { parseArgs } from \"./utils/args.js\";\nimport * as validation from \"./utils/validation.js\";\n\n// ============================================================================\n// Config Schema\n// ============================================================================\n\nexport const Command = Schema.Literal(\n\t\"readme\",\n\t\"topics\",\n\t\"describe\",\n\t\"summary\",\n\t\"tech\",\n\t\"improve\",\n);\nexport type Command = Schema.Schema.Type<typeof Command>;\n\nexport const OutputFormat = Schema.Literal(\"md\", \"mdx\");\nexport type OutputFormat = Schema.Schema.Type<typeof OutputFormat>;\n\nexport const DocumentationStyle = Schema.Literal(\n\t\"minimal\",\n\t\"standard\",\n\t\"comprehensive\",\n);\nexport type DocumentationStyle = Schema.Schema.Type<typeof DocumentationStyle>;\n\nexport const GrepoConfig = Schema.Struct({\n\tbranch: Schema.String,\n\tcommand: Command,\n\tgeminiApiKey: Schema.String,\n\tgithubToken: Schema.optional(Schema.String),\n\tisDryRun: Schema.Boolean,\n\toutputFile: Schema.String,\n\toutputFormat: OutputFormat,\n\trepoUrl: Schema.String,\n\tshouldApply: Schema.Boolean,\n\tshouldMerge: Schema.Boolean,\n\tshouldPush: Schema.Boolean,\n\tstyle: DocumentationStyle,\n\ttone: Schema.optional(\n\t\tSchema.Literal(\"casual\", \"minimal\", \"professional\", \"technical\"),\n\t),\n});\nexport type GrepoConfig = Schema.Schema.Type<typeof GrepoConfig>;\n\n// ============================================================================\n// Env Loading\n// ============================================================================\n\nexport async function loadEnv(): Promise<void> {\n\tconst __filename = fileURLToPath(import.meta.url);\n\tconst __dirname = dirname(__filename);\n\tconst envPath = resolve(__dirname, \"../.env\");\n\n\tif (!existsSync(envPath)) {\n\t\treturn;\n\t}\n\n\tconst envContent = readFileSync(envPath, \"utf-8\");\n\tfor (const line of envContent.split(\"\\n\")) {\n\t\tconst trimmed = line.trim();\n\t\tif (!trimmed || trimmed.startsWith(\"#\")) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst [key, ...valueParts] = trimmed.split(\"=\");\n\t\tif (key && valueParts.length > 0) {\n\t\t\tconst value = valueParts\n\t\t\t\t.join(\"=\")\n\t\t\t\t.trim()\n\t\t\t\t.replaceAll(/(^[\"'])|(['\"]$)/g, \"\");\n\t\t\tif (!process.env[key.trim()]) {\n\t\t\t\tprocess.env[key.trim()] = value;\n\t\t\t}\n\t\t}\n\t}\n}\n\n// ============================================================================\n// Config Builder\n// ============================================================================\n\nconst BOOLEAN_FLAGS = [\"push\", \"apply\", \"merge\", \"dry-run\"] as const;\n\nconst USAGE = `Usage: grepo <command> <github-url> [options]\n\nCommands:\n readme Generate README documentation\n topics Generate and apply repository topics\n describe Generate repository description and homepage URL\n summary Summarize repository\n tech List technologies used\n improve Suggest improvements\n\nOptions:\n --format md|mdx Output format (readme only, default: md)\n --style minimal|standard|comprehensive Documentation style (readme only, default: standard)\n --output <file> Output file path (readme only)\n --push Push to GitHub (readme only)\n --apply Apply changes to GitHub (topics, describe)\n --merge Merge with existing topics (topics only)\n --dry-run Preview changes without applying\n --branch <name> Target branch (default:
|
|
1
|
+
{"version":3,"file":"config.js","names":["validation.isValidGitHubUrl","validation.isValidGeminiApiKey"],"sources":["../src/config.ts"],"sourcesContent":["/**\n *\n * Copyright 2026 Mike Odnis\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { dirname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { Schema } from \"effect\";\n\nimport { GrepoValidationError } from \"./errors.js\";\nimport { parseArgs } from \"./utils/args.js\";\nimport { loadConfigFile } from \"./utils/config-file.js\";\nimport * as validation from \"./utils/validation.js\";\n\n// ============================================================================\n// Config Schema\n// ============================================================================\n\nexport const Command = Schema.Literal(\n\t\"readme\",\n\t\"topics\",\n\t\"describe\",\n\t\"summary\",\n\t\"tech\",\n\t\"improve\",\n);\nexport type Command = Schema.Schema.Type<typeof Command>;\n\nexport const OutputFormat = Schema.Literal(\"md\", \"mdx\");\nexport type OutputFormat = Schema.Schema.Type<typeof OutputFormat>;\n\nexport const DocumentationStyle = Schema.Literal(\n\t\"minimal\",\n\t\"standard\",\n\t\"comprehensive\",\n);\nexport type DocumentationStyle = Schema.Schema.Type<typeof DocumentationStyle>;\n\nexport const GrepoConfig = Schema.Struct({\n\tbranch: Schema.optional(Schema.String),\n\tcommand: Command,\n\tgeminiApiKey: Schema.String,\n\tgithubToken: Schema.optional(Schema.String),\n\tisDryRun: Schema.Boolean,\n\toutputFile: Schema.String,\n\toutputFormat: OutputFormat,\n\trepoUrl: Schema.String,\n\tshouldApply: Schema.Boolean,\n\tshouldMerge: Schema.Boolean,\n\tshouldPush: Schema.Boolean,\n\tstyle: DocumentationStyle,\n\ttone: Schema.optional(\n\t\tSchema.Literal(\"casual\", \"minimal\", \"professional\", \"technical\"),\n\t),\n});\nexport type GrepoConfig = Schema.Schema.Type<typeof GrepoConfig>;\n\n// ============================================================================\n// Env Loading\n// ============================================================================\n\nexport async function loadEnv(): Promise<void> {\n\tconst __filename = fileURLToPath(import.meta.url);\n\tconst __dirname = dirname(__filename);\n\tconst envPath = resolve(__dirname, \"../.env\");\n\n\tif (!existsSync(envPath)) {\n\t\treturn;\n\t}\n\n\tconst envContent = readFileSync(envPath, \"utf-8\");\n\tfor (const line of envContent.split(\"\\n\")) {\n\t\tconst trimmed = line.trim();\n\t\tif (!trimmed || trimmed.startsWith(\"#\")) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst [key, ...valueParts] = trimmed.split(\"=\");\n\t\tif (key && valueParts.length > 0) {\n\t\t\tconst value = valueParts\n\t\t\t\t.join(\"=\")\n\t\t\t\t.trim()\n\t\t\t\t.replaceAll(/(^[\"'])|(['\"]$)/g, \"\");\n\t\t\tif (!process.env[key.trim()]) {\n\t\t\t\tprocess.env[key.trim()] = value;\n\t\t\t}\n\t\t}\n\t}\n}\n\n// ============================================================================\n// Config Builder\n// ============================================================================\n\nconst BOOLEAN_FLAGS = [\"push\", \"apply\", \"merge\", \"dry-run\"] as const;\n\nconst USAGE = `Usage: grepo <command> <github-url> [options]\n\nCommands:\n readme Generate README documentation\n topics Generate and apply repository topics\n describe Generate repository description and homepage URL\n summary Summarize repository\n tech List technologies used\n improve Suggest improvements\n\nOptions:\n --format md|mdx Output format (readme only, default: md)\n --style minimal|standard|comprehensive Documentation style (readme only, default: standard)\n --output <file> Output file path (readme only)\n --push Push to GitHub (readme only)\n --apply Apply changes to GitHub (topics, describe)\n --merge Merge with existing topics (topics only)\n --dry-run Preview changes without applying\n --branch <name> Target branch (default: auto-detect from repo)\n --tone <voice> Tone: casual, professional, minimal, technical (default: auto-detect)`;\n\nconst VALID_TONES = [\"casual\", \"professional\", \"minimal\", \"technical\"] as const;\n\nfunction validateTone(tone: string | undefined): GrepoConfig[\"tone\"] {\n\tif (!tone) {\n\t\treturn undefined;\n\t}\n\tif (!VALID_TONES.includes(tone as (typeof VALID_TONES)[number])) {\n\t\tthrow new GrepoValidationError({\n\t\t\tfield: \"tone\",\n\t\t\tmessage: `Invalid tone \"${tone}\". Must be one of: ${VALID_TONES.join(\", \")}`,\n\t\t});\n\t}\n\treturn tone as GrepoConfig[\"tone\"];\n}\n\nexport function buildConfig(argv: string[]): GrepoConfig {\n\tconst { options, positional } = parseArgs(argv, [...BOOLEAN_FLAGS]);\n\n\tif (positional.length < 2) {\n\t\tconsole.log(USAGE);\n\t\tprocess.exit(1);\n\t}\n\n\tconst command = positional[0];\n\tconst validCommands: Command[] = [\n\t\t\"readme\",\n\t\t\"topics\",\n\t\t\"describe\",\n\t\t\"summary\",\n\t\t\"tech\",\n\t\t\"improve\",\n\t];\n\tif (!validCommands.includes(command as Command)) {\n\t\tconsole.error(`Unknown command: ${command}`);\n\t\tconsole.log(USAGE);\n\t\tprocess.exit(1);\n\t}\n\n\tconst repoUrl = positional[1];\n\tif (!validation.isValidGitHubUrl(repoUrl)) {\n\t\tthrow new GrepoValidationError({\n\t\t\tfield: \"repoUrl\",\n\t\t\tmessage: \"Invalid GitHub URL\",\n\t\t});\n\t}\n\n\tconst configFile = loadConfigFile();\n\n\tconst geminiApiKey =\n\t\tprocess.env.GEMINI_API_KEY ??\n\t\tprocess.env.GOOGLE_API_KEY ??\n\t\tconfigFile.geminiApiKey ??\n\t\t\"\";\n\tif (!validation.isValidGeminiApiKey(geminiApiKey)) {\n\t\tthrow new GrepoValidationError({\n\t\t\tmessage: \"GEMINI_API_KEY is missing or invalid\",\n\t\t});\n\t}\n\n\tconst githubToken =\n\t\tprocess.env.GITHUB_TOKEN ?? process.env.GH_TOKEN ?? configFile.githubToken;\n\tconst shouldApply = !!options.apply;\n\tconst shouldPush = !!options.push;\n\tconst shouldMerge = !!options.merge;\n\n\tif ((shouldApply || shouldPush || shouldMerge) && !githubToken) {\n\t\tthrow new GrepoValidationError({\n\t\t\tmessage:\n\t\t\t\t\"GitHub token (GITHUB_TOKEN or GH_TOKEN) is required for mutation operations\",\n\t\t});\n\t}\n\n\tconst format = (options.format as string) || \"md\";\n\treturn {\n\t\tbranch: (options.branch as string) || undefined,\n\t\tcommand: command as Command,\n\t\tgeminiApiKey,\n\t\tgithubToken,\n\t\tisDryRun: !!options[\"dry-run\"],\n\t\toutputFile: (options.output as string) || `README.${format}`,\n\t\toutputFormat: format as OutputFormat,\n\t\trepoUrl,\n\t\tshouldApply,\n\t\tshouldMerge,\n\t\tshouldPush,\n\t\tstyle: ((options.style as string) || \"standard\") as DocumentationStyle,\n\t\ttone: validateTone(options.tone as string | undefined),\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,MAAa,UAAU,OAAO,QAC7B,UACA,UACA,YACA,WACA,QACA,UACA;AAGD,MAAa,eAAe,OAAO,QAAQ,MAAM,MAAM;AAGvD,MAAa,qBAAqB,OAAO,QACxC,WACA,YACA,gBACA;AAGD,MAAa,cAAc,OAAO,OAAO;CACxC,QAAQ,OAAO,SAAS,OAAO,OAAO;CACtC,SAAS;CACT,cAAc,OAAO;CACrB,aAAa,OAAO,SAAS,OAAO,OAAO;CAC3C,UAAU,OAAO;CACjB,YAAY,OAAO;CACnB,cAAc;CACd,SAAS,OAAO;CAChB,aAAa,OAAO;CACpB,aAAa,OAAO;CACpB,YAAY,OAAO;CACnB,OAAO;CACP,MAAM,OAAO,SACZ,OAAO,QAAQ,UAAU,WAAW,gBAAgB,YAAY,CAChE;CACD,CAAC;AAOF,eAAsB,UAAyB;CAG9C,MAAM,UAAU,QADE,QADC,cAAc,OAAO,KAAK,IAAI,CACZ,EACF,UAAU;AAE7C,KAAI,CAAC,WAAW,QAAQ,CACvB;CAGD,MAAM,aAAa,aAAa,SAAS,QAAQ;AACjD,MAAK,MAAM,QAAQ,WAAW,MAAM,KAAK,EAAE;EAC1C,MAAM,UAAU,KAAK,MAAM;AAC3B,MAAI,CAAC,WAAW,QAAQ,WAAW,IAAI,CACtC;EAED,MAAM,CAAC,KAAK,GAAG,cAAc,QAAQ,MAAM,IAAI;AAC/C,MAAI,OAAO,WAAW,SAAS,GAAG;GACjC,MAAM,QAAQ,WACZ,KAAK,IAAI,CACT,MAAM,CACN,WAAW,oBAAoB,GAAG;AACpC,OAAI,CAAC,QAAQ,IAAI,IAAI,MAAM,EAC1B,SAAQ,IAAI,IAAI,MAAM,IAAI;;;;AAU9B,MAAM,gBAAgB;CAAC;CAAQ;CAAS;CAAS;CAAU;AAE3D,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;AAqBd,MAAM,cAAc;CAAC;CAAU;CAAgB;CAAW;CAAY;AAEtE,SAAS,aAAa,MAA+C;AACpE,KAAI,CAAC,KACJ;AAED,KAAI,CAAC,YAAY,SAAS,KAAqC,CAC9D,OAAM,IAAI,qBAAqB;EAC9B,OAAO;EACP,SAAS,iBAAiB,KAAK,qBAAqB,YAAY,KAAK,KAAK;EAC1E,CAAC;AAEH,QAAO;;AAGR,SAAgB,YAAY,MAA6B;CACxD,MAAM,EAAE,SAAS,eAAe,UAAU,MAAM,CAAC,GAAG,cAAc,CAAC;AAEnE,KAAI,WAAW,SAAS,GAAG;AAC1B,UAAQ,IAAI,MAAM;AAClB,UAAQ,KAAK,EAAE;;CAGhB,MAAM,UAAU,WAAW;AAS3B,KAAI,CAR6B;EAChC;EACA;EACA;EACA;EACA;EACA;EACA,CACkB,SAAS,QAAmB,EAAE;AAChD,UAAQ,MAAM,oBAAoB,UAAU;AAC5C,UAAQ,IAAI,MAAM;AAClB,UAAQ,KAAK,EAAE;;CAGhB,MAAM,UAAU,WAAW;AAC3B,KAAI,CAACA,iBAA4B,QAAQ,CACxC,OAAM,IAAI,qBAAqB;EAC9B,OAAO;EACP,SAAS;EACT,CAAC;CAGH,MAAM,aAAa,gBAAgB;CAEnC,MAAM,eACL,QAAQ,IAAI,kBACZ,QAAQ,IAAI,kBACZ,WAAW,gBACX;AACD,KAAI,CAACC,oBAA+B,aAAa,CAChD,OAAM,IAAI,qBAAqB,EAC9B,SAAS,wCACT,CAAC;CAGH,MAAM,cACL,QAAQ,IAAI,gBAAgB,QAAQ,IAAI,YAAY,WAAW;CAChE,MAAM,cAAc,CAAC,CAAC,QAAQ;CAC9B,MAAM,aAAa,CAAC,CAAC,QAAQ;CAC7B,MAAM,cAAc,CAAC,CAAC,QAAQ;AAE9B,MAAK,eAAe,cAAc,gBAAgB,CAAC,YAClD,OAAM,IAAI,qBAAqB,EAC9B,SACC,+EACD,CAAC;CAGH,MAAM,SAAU,QAAQ,UAAqB;AAC7C,QAAO;EACN,QAAS,QAAQ,UAAqB,KAAA;EAC7B;EACT;EACA;EACA,UAAU,CAAC,CAAC,QAAQ;EACpB,YAAa,QAAQ,UAAqB,UAAU;EACpD,cAAc;EACd;EACA;EACA;EACA;EACA,OAAS,QAAQ,SAAoB;EACrC,MAAM,aAAa,QAAQ,KAA2B;EACtD"}
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { GeminiError, GitHubError, GitIngestError, GrepoValidationError } from "./errors.js";
|
|
2
|
-
import { buildConfig, loadEnv } from "./config.js";
|
|
3
2
|
import { Logger } from "./utils/logger.js";
|
|
3
|
+
import { buildConfig, loadEnv } from "./config.js";
|
|
4
4
|
import { GeminiService } from "./utils/gemini.js";
|
|
5
5
|
import { GitHubClient } from "./utils/github.js";
|
|
6
6
|
import { fetchRepositoryContent } from "./utils/gitingest.js";
|
package/lib/mermaid.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mermaid.d.ts","names":[],"sources":["../src/mermaid.ts"],"sourcesContent":[],"mappings":";;;;;cA+Ba,cAAY,MAAA,CAAA;;;;;;KAMb,YAAA,GAAe,MAAA,CAAO,MAAA,CAAO,YAAY;iBAErC,oBAAA,oBAAwC;
|
|
1
|
+
{"version":3,"file":"mermaid.d.ts","names":[],"sources":["../src/mermaid.ts"],"sourcesContent":[],"mappings":";;;;;cA+Ba,cAAY,MAAA,CAAA;;;;;;KAMb,YAAA,GAAe,MAAA,CAAO,MAAA,CAAO,YAAY;iBAErC,oBAAA,oBAAwC;cA6F3C,iDAEJ,0CAEN,MAAA,CAAO"}
|
package/lib/mermaid.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Logger as Logger$1 } from "./utils/logger.js";
|
|
2
2
|
import { Effect, Schema } from "effect";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
4
5
|
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
5
6
|
import { execFile } from "node:child_process";
|
|
6
|
-
import { tmpdir } from "node:os";
|
|
7
7
|
import { promisify } from "node:util";
|
|
8
8
|
//#region src/mermaid.ts
|
|
9
9
|
/**
|
|
@@ -44,11 +44,34 @@ function extractMermaidBlocks(markdown) {
|
|
|
44
44
|
return blocks;
|
|
45
45
|
}
|
|
46
46
|
async function validateBlock(block) {
|
|
47
|
-
|
|
47
|
+
let dir;
|
|
48
|
+
try {
|
|
49
|
+
dir = await mkdtemp(join(tmpdir(), "mermaid-"));
|
|
50
|
+
} catch (err) {
|
|
51
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
52
|
+
logger.warn(`Failed to create temp directory: ${message}`);
|
|
53
|
+
return {
|
|
54
|
+
error: `Temp directory creation failed: ${message}`,
|
|
55
|
+
valid: false
|
|
56
|
+
};
|
|
57
|
+
}
|
|
48
58
|
const inputFile = join(dir, "input.mmd");
|
|
49
59
|
const outputFile = join(dir, "output.svg");
|
|
50
60
|
try {
|
|
51
61
|
await writeFile(inputFile, block.code);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
64
|
+
logger.warn(`Failed to write mermaid input file: ${message}`);
|
|
65
|
+
await rm(dir, {
|
|
66
|
+
force: true,
|
|
67
|
+
recursive: true
|
|
68
|
+
}).catch(() => {});
|
|
69
|
+
return {
|
|
70
|
+
error: `File write failed: ${message}`,
|
|
71
|
+
valid: false
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
52
75
|
await execFileAsync("npx", [
|
|
53
76
|
"mmdc",
|
|
54
77
|
"-i",
|
|
@@ -98,6 +121,7 @@ const validateAndFixMermaid = (content, gemini, maxRetries = 2) => Effect.gen(fu
|
|
|
98
121
|
if (blocks.length === 0) return content;
|
|
99
122
|
logger.info(`Validating ${blocks.length} Mermaid diagram(s)...`);
|
|
100
123
|
let result = content;
|
|
124
|
+
let offset = 0;
|
|
101
125
|
for (const block of blocks) {
|
|
102
126
|
let currentCode = block.code;
|
|
103
127
|
let attempt = 0;
|
|
@@ -109,19 +133,27 @@ const validateAndFixMermaid = (content, gemini, maxRetries = 2) => Effect.gen(fu
|
|
|
109
133
|
attempt++;
|
|
110
134
|
logger.warn(`Diagram ${block.index + 1} invalid (attempt ${attempt}/${maxRetries}): ${validation.error}`);
|
|
111
135
|
const fixPrompt = buildFixPrompt(currentCode, validation.error ?? "Unknown error");
|
|
112
|
-
currentCode = (yield* Effect.catchAll(gemini.generateContent(fixPrompt), () =>
|
|
136
|
+
currentCode = (yield* Effect.catchAll(gemini.generateContent(fixPrompt), (err) => {
|
|
137
|
+
logger.warn(`Diagram ${block.index + 1}: Gemini fix failed (${err.message}), keeping current code`);
|
|
138
|
+
return Effect.succeed(currentCode);
|
|
139
|
+
})).replace(/^```(?:mermaid)?\s*/i, "").replace(/\s*```\s*$/, "").trim();
|
|
113
140
|
validation = yield* Effect.promise(() => validateBlock({
|
|
114
141
|
...block,
|
|
115
142
|
code: currentCode
|
|
116
143
|
}));
|
|
117
144
|
}
|
|
145
|
+
const start = block.start + offset;
|
|
146
|
+
const end = block.end + offset;
|
|
118
147
|
if (validation.valid && currentCode !== block.code) {
|
|
119
148
|
logger.success(`Diagram ${block.index + 1} fixed after ${attempt} attempt(s)`);
|
|
120
|
-
|
|
149
|
+
const replacement = `\`\`\`mermaid\n${currentCode}\n\`\`\``;
|
|
150
|
+
result = result.slice(0, start) + replacement + result.slice(end);
|
|
151
|
+
offset += replacement.length - (block.end - block.start);
|
|
121
152
|
} else if (validation.valid) logger.success(`Diagram ${block.index + 1} valid`);
|
|
122
153
|
else {
|
|
123
|
-
logger.warn(`Diagram ${block.index + 1} could not be fixed, removing it`);
|
|
124
|
-
result = result.
|
|
154
|
+
logger.warn(`Diagram ${block.index + 1} could not be fixed after ${attempt} attempt(s), removing it: ${validation.error}`);
|
|
155
|
+
result = result.slice(0, start) + result.slice(end);
|
|
156
|
+
offset -= block.end - block.start;
|
|
125
157
|
}
|
|
126
158
|
}
|
|
127
159
|
return result;
|