@elysiumoss/grepo 0.3.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/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
 
@@ -46,9 +56,12 @@ if (!resolvedBranch) try {
46
56
  const { owner, repo } = parseGitHubUrl(parsedConfig.repoUrl);
47
57
  resolvedBranch = await new GitHubClient(parsedConfig.githubToken).getDefaultBranch(owner, repo);
48
58
  logger.info(`Detected default branch: ${resolvedBranch}`);
49
- } catch {
59
+ } catch (err) {
50
60
  resolvedBranch = "main";
51
- logger.warn("Could not detect default branch, falling back to '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
+ });
52
65
  }
53
66
  const config = {
54
67
  ...parsedConfig,
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 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 {\n\t\tresolvedBranch = \"main\";\n\t\tlogger.warn(\"Could not detect default branch, falling back to 'main'\");\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":";;;;;;;;;;;;;;;;;;;;;;;AAiCA,MAAM,SAAS,IAAIA,SAAO,QAAQ;AAElC,MAAM,SAAS;AAEf,MAAM,OAAO,QAAQ,KAAK,MAAM,EAAE;AAElC,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;QAClD;AACP,kBAAiB;AACjB,QAAO,KAAK,0DAA0D;;AAIxE,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"}
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"}
@@ -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,QAAQ;AACjD,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"}
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"}
@@ -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,QAAQ;AACjD,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"}
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"}
@@ -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);
@@ -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 ?? \"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,QAAQ;AACjD,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"}
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"}
@@ -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,QAAQ;AACjD,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"}
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"}
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","names":[],"sources":["../src/config.ts"],"sourcesContent":[],"mappings":";;;;cA+Ba,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"}
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";
@@ -119,9 +120,10 @@ function buildConfig(argv) {
119
120
  field: "repoUrl",
120
121
  message: "Invalid GitHub URL"
121
122
  });
122
- const geminiApiKey = process.env.GEMINI_API_KEY ?? process.env.GOOGLE_API_KEY ?? "";
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;
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.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 geminiApiKey =\n\t\tprocess.env.GEMINI_API_KEY ?? process.env.GOOGLE_API_KEY ?? \"\";\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 = process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN;\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":"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"}
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":"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"}
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";
@@ -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;cA2E3C,iDAEJ,0CAEN,MAAA,CAAO"}
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
- const dir = await mkdtemp(join(tmpdir(), "mermaid-"));
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), () => Effect.succeed(currentCode))).replace(/^```(?:mermaid)?\s*/i, "").replace(/\s*```\s*$/, "").trim();
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
- result = result.replace(`\`\`\`mermaid\n${block.code}\n\`\`\``, `\`\`\`mermaid\n${currentCode}\n\`\`\``);
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.replace(`\`\`\`mermaid\n${block.code}\n\`\`\``, "");
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;
@@ -1 +1 @@
1
- {"version":3,"file":"mermaid.js","names":["Logger"],"sources":["../src/mermaid.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 { execFile } from \"node:child_process\";\nimport { mkdtemp, rm, writeFile } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { promisify } from \"node:util\";\nimport { Effect, Schema } from \"effect\";\n\nimport type { GeminiServiceApi } from \"./services.js\";\nimport { Logger } from \"./utils/logger.js\";\n\nconst execFileAsync = promisify(execFile);\nconst logger = new Logger(\"GREPO:MERMAID\");\n\nexport const MermaidBlock = Schema.Struct({\n\tcode: Schema.String,\n\tend: Schema.Number,\n\tindex: Schema.Number,\n\tstart: Schema.Number,\n});\nexport type MermaidBlock = Schema.Schema.Type<typeof MermaidBlock>;\n\nexport function extractMermaidBlocks(markdown: string): MermaidBlock[] {\n\tconst blocks: MermaidBlock[] = [];\n\tconst regex = /```mermaid\\n([\\s\\S]*?)```/g;\n\tlet index = 0;\n\n\tfor (\n\t\tlet match = regex.exec(markdown);\n\t\tmatch !== null;\n\t\tmatch = regex.exec(markdown)\n\t) {\n\t\tblocks.push({\n\t\t\tcode: match[1].trim(),\n\t\t\tend: match.index + match[0].length,\n\t\t\tindex: index++,\n\t\t\tstart: match.index,\n\t\t});\n\t}\n\n\treturn blocks;\n}\n\nasync function validateBlock(\n\tblock: MermaidBlock,\n): Promise<{ error?: string; valid: boolean }> {\n\tconst dir = await mkdtemp(join(tmpdir(), \"mermaid-\"));\n\tconst inputFile = join(dir, \"input.mmd\");\n\tconst outputFile = join(dir, \"output.svg\");\n\n\ttry {\n\t\tawait writeFile(inputFile, block.code);\n\n\t\tawait execFileAsync(\n\t\t\t\"npx\",\n\t\t\t[\"mmdc\", \"-i\", inputFile, \"-o\", outputFile, \"-e\", \"svg\"],\n\t\t\t{ timeout: 15_000 },\n\t\t);\n\n\t\treturn { valid: true };\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tconst stderr =\n\t\t\terr &&\n\t\t\ttypeof err === \"object\" &&\n\t\t\t\"stderr\" in err &&\n\t\t\ttypeof err.stderr === \"string\"\n\t\t\t\t? err.stderr\n\t\t\t\t: \"\";\n\t\treturn { error: stderr.trim() || message, valid: false };\n\t} finally {\n\t\tawait rm(dir, { force: true, recursive: true }).catch(() => {});\n\t}\n}\n\nfunction buildFixPrompt(code: string, error: string): string {\n\treturn `The following Mermaid diagram has a syntax error. Fix it and return ONLY the corrected Mermaid code — no fences, no explanation.\n\n<broken_diagram>\n${code}\n</broken_diagram>\n\n<error>\n${error}\n</error>\n\nCommon fixes:\n- Wrap labels with special chars in quotes: A[\"Label (info)\"]\n- Use --> not -> in flowcharts\n- Use ->> not --> in sequence diagrams\n- Use \"flowchart\" not \"graph\"\n- Subgraph IDs cannot have spaces\n- No semicolons at end of lines\n\nReturn ONLY the fixed Mermaid code.`;\n}\n\nexport const validateAndFixMermaid = (\n\tcontent: string,\n\tgemini: GeminiServiceApi,\n\tmaxRetries = 2,\n): Effect.Effect<string, never> =>\n\tEffect.gen(function* () {\n\t\tconst blocks = extractMermaidBlocks(content);\n\t\tif (blocks.length === 0) {\n\t\t\treturn content;\n\t\t}\n\n\t\tlogger.info(`Validating ${blocks.length} Mermaid diagram(s)...`);\n\t\tlet result = content;\n\n\t\tfor (const block of blocks) {\n\t\t\tlet currentCode = block.code;\n\t\t\tlet attempt = 0;\n\t\t\tlet validation = yield* Effect.promise(() =>\n\t\t\t\tvalidateBlock({ ...block, code: currentCode }),\n\t\t\t);\n\n\t\t\twhile (!validation.valid && attempt < maxRetries) {\n\t\t\t\tattempt++;\n\t\t\t\tlogger.warn(\n\t\t\t\t\t`Diagram ${block.index + 1} invalid (attempt ${attempt}/${maxRetries}): ${validation.error}`,\n\t\t\t\t);\n\n\t\t\t\tconst fixPrompt = buildFixPrompt(\n\t\t\t\t\tcurrentCode,\n\t\t\t\t\tvalidation.error ?? \"Unknown error\",\n\t\t\t\t);\n\t\t\t\tconst fixed: string = yield* Effect.catchAll(\n\t\t\t\t\tgemini.generateContent(fixPrompt),\n\t\t\t\t\t() => Effect.succeed(currentCode),\n\t\t\t\t);\n\n\t\t\t\tcurrentCode = fixed\n\t\t\t\t\t.replace(/^```(?:mermaid)?\\s*/i, \"\")\n\t\t\t\t\t.replace(/\\s*```\\s*$/, \"\")\n\t\t\t\t\t.trim();\n\n\t\t\t\tvalidation = yield* Effect.promise(() =>\n\t\t\t\t\tvalidateBlock({ ...block, code: currentCode }),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (validation.valid && currentCode !== block.code) {\n\t\t\t\tlogger.success(\n\t\t\t\t\t`Diagram ${block.index + 1} fixed after ${attempt} attempt(s)`,\n\t\t\t\t);\n\t\t\t\tresult = result.replace(\n\t\t\t\t\t`\\`\\`\\`mermaid\\n${block.code}\\n\\`\\`\\``,\n\t\t\t\t\t`\\`\\`\\`mermaid\\n${currentCode}\\n\\`\\`\\``,\n\t\t\t\t);\n\t\t\t} else if (validation.valid) {\n\t\t\t\tlogger.success(`Diagram ${block.index + 1} valid`);\n\t\t\t} else {\n\t\t\t\tlogger.warn(\n\t\t\t\t\t`Diagram ${block.index + 1} could not be fixed, removing it`,\n\t\t\t\t);\n\t\t\t\tresult = result.replace(`\\`\\`\\`mermaid\\n${block.code}\\n\\`\\`\\``, \"\");\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t});\n"],"mappings":"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"}
1
+ {"version":3,"file":"mermaid.js","names":["Logger"],"sources":["../src/mermaid.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 { execFile } from \"node:child_process\";\nimport { mkdtemp, rm, writeFile } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { promisify } from \"node:util\";\nimport { Effect, Schema } from \"effect\";\n\nimport type { GeminiServiceApi } from \"./services.js\";\nimport { Logger } from \"./utils/logger.js\";\n\nconst execFileAsync = promisify(execFile);\nconst logger = new Logger(\"GREPO:MERMAID\");\n\nexport const MermaidBlock = Schema.Struct({\n\tcode: Schema.String,\n\tend: Schema.Number,\n\tindex: Schema.Number,\n\tstart: Schema.Number,\n});\nexport type MermaidBlock = Schema.Schema.Type<typeof MermaidBlock>;\n\nexport function extractMermaidBlocks(markdown: string): MermaidBlock[] {\n\tconst blocks: MermaidBlock[] = [];\n\tconst regex = /```mermaid\\n([\\s\\S]*?)```/g;\n\tlet index = 0;\n\n\tfor (\n\t\tlet match = regex.exec(markdown);\n\t\tmatch !== null;\n\t\tmatch = regex.exec(markdown)\n\t) {\n\t\tblocks.push({\n\t\t\tcode: match[1].trim(),\n\t\t\tend: match.index + match[0].length,\n\t\t\tindex: index++,\n\t\t\tstart: match.index,\n\t\t});\n\t}\n\n\treturn blocks;\n}\n\nasync function validateBlock(\n\tblock: MermaidBlock,\n): Promise<{ error?: string; valid: boolean }> {\n\tlet dir: string;\n\ttry {\n\t\tdir = await mkdtemp(join(tmpdir(), \"mermaid-\"));\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tlogger.warn(`Failed to create temp directory: ${message}`);\n\t\treturn {\n\t\t\terror: `Temp directory creation failed: ${message}`,\n\t\t\tvalid: false,\n\t\t};\n\t}\n\n\tconst inputFile = join(dir, \"input.mmd\");\n\tconst outputFile = join(dir, \"output.svg\");\n\n\ttry {\n\t\tawait writeFile(inputFile, block.code);\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tlogger.warn(`Failed to write mermaid input file: ${message}`);\n\t\tawait rm(dir, { force: true, recursive: true }).catch(() => {});\n\t\treturn { error: `File write failed: ${message}`, valid: false };\n\t}\n\n\ttry {\n\t\tawait execFileAsync(\n\t\t\t\"npx\",\n\t\t\t[\"mmdc\", \"-i\", inputFile, \"-o\", outputFile, \"-e\", \"svg\"],\n\t\t\t{ timeout: 15_000 },\n\t\t);\n\n\t\treturn { valid: true };\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tconst stderr =\n\t\t\terr &&\n\t\t\ttypeof err === \"object\" &&\n\t\t\t\"stderr\" in err &&\n\t\t\ttypeof err.stderr === \"string\"\n\t\t\t\t? err.stderr\n\t\t\t\t: \"\";\n\t\treturn { error: stderr.trim() || message, valid: false };\n\t} finally {\n\t\tawait rm(dir, { force: true, recursive: true }).catch(() => {});\n\t}\n}\n\nfunction buildFixPrompt(code: string, error: string): string {\n\treturn `The following Mermaid diagram has a syntax error. Fix it and return ONLY the corrected Mermaid code — no fences, no explanation.\n\n<broken_diagram>\n${code}\n</broken_diagram>\n\n<error>\n${error}\n</error>\n\nCommon fixes:\n- Wrap labels with special chars in quotes: A[\"Label (info)\"]\n- Use --> not -> in flowcharts\n- Use ->> not --> in sequence diagrams\n- Use \"flowchart\" not \"graph\"\n- Subgraph IDs cannot have spaces\n- No semicolons at end of lines\n\nReturn ONLY the fixed Mermaid code.`;\n}\n\nexport const validateAndFixMermaid = (\n\tcontent: string,\n\tgemini: GeminiServiceApi,\n\tmaxRetries = 2,\n): Effect.Effect<string, never> =>\n\tEffect.gen(function* () {\n\t\tconst blocks = extractMermaidBlocks(content);\n\t\tif (blocks.length === 0) {\n\t\t\treturn content;\n\t\t}\n\n\t\tlogger.info(`Validating ${blocks.length} Mermaid diagram(s)...`);\n\t\tlet result = content;\n\t\tlet offset = 0;\n\n\t\tfor (const block of blocks) {\n\t\t\tlet currentCode = block.code;\n\t\t\tlet attempt = 0;\n\t\t\tlet validation = yield* Effect.promise(() =>\n\t\t\t\tvalidateBlock({ ...block, code: currentCode }),\n\t\t\t);\n\n\t\t\twhile (!validation.valid && attempt < maxRetries) {\n\t\t\t\tattempt++;\n\t\t\t\tlogger.warn(\n\t\t\t\t\t`Diagram ${block.index + 1} invalid (attempt ${attempt}/${maxRetries}): ${validation.error}`,\n\t\t\t\t);\n\n\t\t\t\tconst fixPrompt = buildFixPrompt(\n\t\t\t\t\tcurrentCode,\n\t\t\t\t\tvalidation.error ?? \"Unknown error\",\n\t\t\t\t);\n\t\t\t\tconst fixed: string = yield* Effect.catchAll(\n\t\t\t\t\tgemini.generateContent(fixPrompt),\n\t\t\t\t\t(err) => {\n\t\t\t\t\t\tlogger.warn(\n\t\t\t\t\t\t\t`Diagram ${block.index + 1}: Gemini fix failed (${err.message}), keeping current code`,\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn Effect.succeed(currentCode);\n\t\t\t\t\t},\n\t\t\t\t);\n\n\t\t\t\tcurrentCode = fixed\n\t\t\t\t\t.replace(/^```(?:mermaid)?\\s*/i, \"\")\n\t\t\t\t\t.replace(/\\s*```\\s*$/, \"\")\n\t\t\t\t\t.trim();\n\n\t\t\t\tvalidation = yield* Effect.promise(() =>\n\t\t\t\t\tvalidateBlock({ ...block, code: currentCode }),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst start = block.start + offset;\n\t\t\tconst end = block.end + offset;\n\n\t\t\tif (validation.valid && currentCode !== block.code) {\n\t\t\t\tlogger.success(\n\t\t\t\t\t`Diagram ${block.index + 1} fixed after ${attempt} attempt(s)`,\n\t\t\t\t);\n\t\t\t\tconst replacement = `\\`\\`\\`mermaid\\n${currentCode}\\n\\`\\`\\``;\n\t\t\t\tresult = result.slice(0, start) + replacement + result.slice(end);\n\t\t\t\toffset += replacement.length - (block.end - block.start);\n\t\t\t} else if (validation.valid) {\n\t\t\t\tlogger.success(`Diagram ${block.index + 1} valid`);\n\t\t\t} else {\n\t\t\t\tlogger.warn(\n\t\t\t\t\t`Diagram ${block.index + 1} could not be fixed after ${attempt} attempt(s), removing it: ${validation.error}`,\n\t\t\t\t);\n\t\t\t\tresult = result.slice(0, start) + result.slice(end);\n\t\t\t\toffset -= block.end - block.start;\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t});\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"readme.d.ts","names":[],"sources":["../../src/prompts/readme.ts"],"sourcesContent":[],"mappings":";;;;;cA2Ba,eAAa,MAAA,CAAA;EAOO,QAAA,eAAA,CAAA,oBAAA,CAAA;EAAA,OAAA,EAAA,oBAAA;EAOpB,KAAA,EAAA,oBAIX;;;cAXW,sBAAoB,MAAA,CAAA;;EAON,OAAA,eAAA,CAAA,oBAAA,CAAA;EAAA,QAAA,eAAA,CAAA,oBAAA,CAAA;EAMd,OAAA,gBAQX,CAAA,CAAA,QAAA,EAAA,MAAA,EAAA,MAAA,EAAA,MAAA,CAAA,CAAA;;cAdW,gBAAc,MAAA,CAAA;;;;;AAMA,cAAd,cAAc,EAAA,MAAA,CAAA,MAAA,CAAA;EAAA,WAAA,eAAA,CAAA,oBAAA,CAAA;EAUd,IAAA,eAKZ,cALsB,CAAA;IAMX,MAAI,EAAA,oBAAG;IAEN,OAAA,EAAA,oBAGX;;;AAH8B,cARnB,UAQmB,EART,MAAA,CAAA,OAQS,CAAA,CAAA,QAAA,EAAA,SAAA,EAAA,cAAA,EAAA,WAAA,CAAA,CAAA;AAAA,KAFpB,IAAA,GAAO,MAAA,CAAO,MAAA,CAAO,IAED,CAAA,OAFa,UAEb,CAAA;AAKnB,cALA,mBAYX,EAZ8B,MAAA,CAAA,MAY9B,CAAA;;;;cAPW,gBAAc,MAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAAA,QAAA,EAAA,oBAAA;EAAA,CAAA,CAAA;AAQ3B,CAAA,CAAA;AAEa,KAFD,cAAA,GAAiB,MAAA,CAAO,MAAA,CAAO,IAMzC,CAAA,OANqD,cAMrD,CAAA;cAJW,mBAAiB,MAAA,CAAA;;;;CAAA,CAAA;AAAA,KAKlB,iBAAA,GAAoB,MAAA,CAAO,MAAA,CAAO,IALhB,CAAA,OAK4B,iBAL5B,CAAA;AAKlB,cAMC,QANgB,EAMR,MAAA,CAAA,MANqC,CAAA;EAM7C,OAAA,EAAA,oBAKX;;;;;AALmB,KAMT,QAAA,GAAW,MAAA,CAAO,MAAA,CAAO,IANhB,CAAA,OAM4B,QAN5B,CAAA;AAAA,cAYR,gBAZQ,EAYU,cAZV;AAMT,iBA0CI,qBAAA,CA1Cc,OAAO,EAAA,MAAI,CAAA,EAAA,MAAA,GAAA,IAAA;AAM5B,iBA+CG,aAAA,CA/Ce,GAAA,EAAA,MAAA,CA8B9B,EAiB2C,cAjB3C;AAMe,iBAyCA,mBAAA,CAzCqB,QAAA,EA0C1B,QA1C0B,EAAA,cAAA,CAAA,EAAA,MAAA,CAAA,EAAA,MAAA;AAWrB,cA4HH,cA5H+B,EA4Hf,MA5He,CA4HR,kBA5HsB,EAAA,MAAA,CAAA;AA8B1C,iBAuGA,+BAAA,CAtGG,QAAA,EAuGR,cAvGQ,CAAA,EAAA,MAAA;AA6FN,iBA6CG,qBAAA,CA7CoB,QAAP,EA8ClB,cA9CwB,EAAA,QAAA,EA+CxB,QA/CwB,EAAA,OAAA,EAgDzB,iBAhDyB,CAAA,EAAA,MAAA"}
1
+ {"version":3,"file":"readme.d.ts","names":[],"sources":["../../src/prompts/readme.ts"],"sourcesContent":[],"mappings":";;;;;cA2Ba,eAAa,MAAA,CAAA;EAOO,QAAA,eAAA,CAAA,oBAAA,CAAA;EAAA,OAAA,EAAA,oBAAA;EAOpB,KAAA,EAAA,oBAIX;;;cAXW,sBAAoB,MAAA,CAAA;;EAON,OAAA,eAAA,CAAA,oBAAA,CAAA;EAAA,QAAA,eAAA,CAAA,oBAAA,CAAA;EAMd,OAAA,gBAQX,CAAA,CAAA,QAAA,EAAA,MAAA,EAAA,MAAA,EAAA,MAAA,CAAA,CAAA;;cAdW,gBAAc,MAAA,CAAA;;;;;AAMA,cAAd,cAAc,EAAA,MAAA,CAAA,MAAA,CAAA;EAAA,WAAA,eAAA,CAAA,oBAAA,CAAA;EAUd,IAAA,eAKZ,cALsB,CAAA;IAMX,MAAI,EAAA,oBAAG;IAEN,OAAA,EAAA,oBAGX;;;AAH8B,cARnB,UAQmB,EART,MAAA,CAAA,OAQS,CAAA,CAAA,QAAA,EAAA,SAAA,EAAA,cAAA,EAAA,WAAA,CAAA,CAAA;AAAA,KAFpB,IAAA,GAAO,MAAA,CAAO,MAAA,CAAO,IAED,CAAA,OAFa,UAEb,CAAA;AAKnB,cALA,mBAYX,EAZ8B,MAAA,CAAA,MAY9B,CAAA;;;;cAPW,gBAAc,MAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAAA,QAAA,EAAA,oBAAA;EAAA,CAAA,CAAA;AAQ3B,CAAA,CAAA;AAEa,KAFD,cAAA,GAAiB,MAAA,CAAO,MAAA,CAAO,IAMzC,CAAA,OANqD,cAMrD,CAAA;cAJW,mBAAiB,MAAA,CAAA;;;;CAAA,CAAA;AAAA,KAKlB,iBAAA,GAAoB,MAAA,CAAO,MAAA,CAAO,IALhB,CAAA,OAK4B,iBAL5B,CAAA;AAKlB,cAMC,QANgB,EAMR,MAAA,CAAA,MANqC,CAAA;EAM7C,OAAA,EAAA,oBAKX;;;;;AALmB,KAMT,QAAA,GAAW,MAAA,CAAO,MAAA,CAAO,IANhB,CAAA,OAM4B,QAN5B,CAAA;AAAA,cAYR,gBAZQ,EAYU,cAZV;AAMT,iBA0CI,qBAAA,CA1Cc,OAAO,EAAA,MAAI,CAAA,EAAA,MAAA,GAAA,IAAA;AAM5B,iBA+CG,aAAA,CA/Ce,GAAA,EAAA,MAAA,CA8B9B,EAiB2C,cAjB3C;AAMe,iBA4CA,mBAAA,CA5CqB,QAAA,EA6C1B,QA7C0B,EAAA,cAAA,CAAA,EAAA,MAAA,CAAA,EAAA,MAAA;AAWrB,cA+HH,cA/H+B,EA+Hf,MA/He,CA+HR,kBA/HsB,EAAA,MAAA,CAAA;AAiC1C,iBAuGA,+BAAA,CAtGG,QAAA,EAuGR,cAvGQ,CAAA,EAAA,MAAA;AA6FN,iBA6CG,qBAAA,CA7CoB,QAAP,EA8ClB,cA9CwB,EAAA,QAAA,EA+CxB,QA/CwB,EAAA,OAAA,EAgDzB,iBAhDyB,CAAA,EAAA,MAAA"}
@@ -110,7 +110,11 @@ function parseAnalysis(raw) {
110
110
  if (!result.sections.recommended.length) throw new Error("sections.recommended must not be empty");
111
111
  return result;
112
112
  } catch (err) {
113
- new Logger$1("GREPO:README-PROMPT").warn(`Failed to parse analysis JSON, falling back to DEFAULT_ANALYSIS. Error: ${err instanceof Error ? err.message : String(err)}`);
113
+ new Logger$1("GREPO:README-PROMPT").warn(`Failed to parse analysis JSON, falling back to DEFAULT_ANALYSIS`, {
114
+ error: err instanceof Error ? err.message : String(err),
115
+ rawLength: raw.length,
116
+ rawPreview: raw.slice(0, 200)
117
+ });
114
118
  return DEFAULT_ANALYSIS;
115
119
  }
116
120
  }
@@ -1 +1 @@
1
- {"version":3,"file":"readme.js","names":["Logger"],"sources":["../../src/prompts/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 { Schema } from \"effect\";\n\nimport type { DocumentationStyle } from \"../config.js\";\nimport { Logger } from \"../utils/logger.js\";\n\n// ============================================================================\n// Types & Schemas\n// ============================================================================\n\nexport const DiagramSchema = Schema.Struct({\n\tkeyNodes: Schema.Array(Schema.String),\n\tpurpose: Schema.String,\n\tscope: Schema.String,\n\ttype: Schema.Literal(\"c4-context\", \"er\", \"flowchart\", \"sequence\", \"state\"),\n});\n\nexport const ExistingReadmeSchema = Schema.Struct({\n\timprove: Schema.Array(Schema.String),\n\tmissing: Schema.Array(Schema.String),\n\tpreserve: Schema.Array(Schema.String),\n\tquality: Schema.Literal(\"decent\", \"good\", \"none\", \"poor\"),\n});\n\nexport const IdentitySchema = Schema.Struct({\n\tdifferentiators: Schema.Array(Schema.String),\n\tname: Schema.String,\n\toneLiner: Schema.String,\n});\n\nexport const SectionsSchema = Schema.Struct({\n\trecommended: Schema.Array(Schema.String),\n\tskip: Schema.Array(\n\t\tSchema.Struct({\n\t\t\treason: Schema.String,\n\t\t\tsection: Schema.String,\n\t\t}),\n\t),\n});\n\nexport const ToneSchema = Schema.Literal(\n\t\"casual\",\n\t\"minimal\",\n\t\"professional\",\n\t\"technical\",\n);\nexport type Tone = Schema.Schema.Type<typeof ToneSchema>;\n\nexport const ToneDetectionSchema = Schema.Struct({\n\tdetected: ToneSchema,\n\tevidence: Schema.String,\n});\n\nexport const AnalysisResult = Schema.Struct({\n\tdiagrams: Schema.Array(DiagramSchema),\n\texistingReadme: ExistingReadmeSchema,\n\tidentity: IdentitySchema,\n\trepoType: Schema.String,\n\tsections: SectionsSchema,\n\ttone: ToneDetectionSchema,\n});\nexport type AnalysisResult = Schema.Schema.Type<typeof AnalysisResult>;\n\nexport const GenerationOptions = Schema.Struct({\n\tformat: Schema.Literal(\"md\", \"mdx\"),\n\tstyle: Schema.Literal(\"minimal\", \"standard\", \"comprehensive\"),\n\ttone: Schema.optional(ToneSchema),\n});\nexport type GenerationOptions = Schema.Schema.Type<typeof GenerationOptions>;\n\n// ============================================================================\n// Repo Data Schema\n// ============================================================================\n\nexport const RepoData = Schema.Struct({\n\tcontent: Schema.String,\n\trepo_url: Schema.String,\n\tsummary: Schema.String,\n\ttree: Schema.String,\n});\nexport type RepoData = Schema.Schema.Type<typeof RepoData>;\n\n// ============================================================================\n// Default Fallback\n// ============================================================================\n\nexport const DEFAULT_ANALYSIS: AnalysisResult = {\n\tdiagrams: [],\n\texistingReadme: {\n\t\timprove: [],\n\t\tmissing: [],\n\t\tpreserve: [],\n\t\tquality: \"none\",\n\t},\n\tidentity: {\n\t\tdifferentiators: [],\n\t\tname: \"Unknown\",\n\t\toneLiner: \"\",\n\t},\n\trepoType: \"library\",\n\tsections: {\n\t\trecommended: [\n\t\t\t\"hero\",\n\t\t\t\"overview\",\n\t\t\t\"installation\",\n\t\t\t\"quick-start\",\n\t\t\t\"usage\",\n\t\t\t\"development\",\n\t\t\t\"license\",\n\t\t],\n\t\tskip: [],\n\t},\n\ttone: {\n\t\tdetected: \"professional\",\n\t\tevidence: \"fallback default\",\n\t},\n};\n\n// ============================================================================\n// Utilities\n// ============================================================================\n\nexport function extractExistingReadme(content: string): string | null {\n\tconst readmeMatch =\n\t\t/(?:^|\\n)(?:={3,}|─{3,})?\\s*README\\.md\\s*(?:={3,}|─{3,})?\\n([\\s\\S]*?)(?=\\n(?:={3,}|─{3,})\\s*\\S+\\.\\S+|$)/.exec(\n\t\t\tcontent,\n\t\t);\n\tif (readmeMatch?.[1] && readmeMatch[1].trim().length > 50) {\n\t\treturn readmeMatch[1].trim();\n\t}\n\treturn null;\n}\n\nexport function parseAnalysis(raw: string): AnalysisResult {\n\ttry {\n\t\tconst stripped = raw\n\t\t\t.replace(/^```(?:json)?\\s*/i, \"\")\n\t\t\t.replace(/\\s*```\\s*$/, \"\")\n\t\t\t.trim();\n\n\t\tconst json = JSON.parse(stripped) as unknown;\n\t\tconst result = Schema.decodeUnknownSync(AnalysisResult)(json);\n\n\t\tif (!result.sections.recommended.length) {\n\t\t\tthrow new Error(\"sections.recommended must not be empty\");\n\t\t}\n\n\t\treturn result;\n\t} catch (err) {\n\t\tconst logger = new Logger(\"GREPO:README-PROMPT\");\n\t\tlogger.warn(\n\t\t\t`Failed to parse analysis JSON, falling back to DEFAULT_ANALYSIS. Error: ${\n\t\t\t\terr instanceof Error ? err.message : String(err)\n\t\t\t}`,\n\t\t);\n\t\treturn DEFAULT_ANALYSIS;\n\t}\n}\n\n// ============================================================================\n// Phase 1: Analysis Prompt\n// ============================================================================\n\nexport function buildAnalysisPrompt(\n\trepoData: RepoData,\n\texistingReadme?: string,\n): string {\n\tconst existingReadmeBlock = existingReadme\n\t\t? `\n<existing_readme>\n${existingReadme.slice(0, 4000)}\n</existing_readme>`\n\t\t: \"\";\n\n\treturn `You are an expert technical analyst. Analyze the repository below and return ONLY valid JSON — no markdown fences, no commentary, no extra text.\n\n<output_schema>\n{\n \"repoType\": \"string — e.g. cli-tool, library, web-app, api, monorepo, config, data-pipeline, etc.\",\n \"identity\": {\n \"name\": \"string — canonical project name\",\n \"oneLiner\": \"string — one sentence describing what it does\",\n \"differentiators\": [\"string — unique selling points\"]\n },\n \"tone\": {\n \"detected\": \"casual | professional | minimal | technical\",\n \"evidence\": \"string — quote or observation that led to this conclusion\"\n },\n \"sections\": {\n \"recommended\": [\"string — section keys from the catalog below, in logical order\"],\n \"skip\": [{ \"section\": \"string\", \"reason\": \"string\" }]\n },\n \"diagrams\": [\n {\n \"type\": \"flowchart | sequence | c4-context | er | state\",\n \"purpose\": \"string — what this diagram explains\",\n \"scope\": \"string — what subsystem or flow it covers\",\n \"keyNodes\": [\"string — important nodes or actors\"]\n }\n ],\n \"existingReadme\": {\n \"quality\": \"good | decent | poor | none\",\n \"preserve\": [\"string — sections or content worth keeping verbatim\"],\n \"improve\": [\"string — sections that exist but need rework\"],\n \"missing\": [\"string — important topics not covered\"]\n }\n}\n</output_schema>\n\n<section_catalog>\nhero — project name, tagline, badges\noverview — what it does and why it exists\nfeatures — bullet list of capabilities\narchitecture — system design, component overview\ninstallation — how to install/setup\nquick-start — minimal working example\nusage — detailed usage instructions\ncli-reference — CLI flags and subcommands\napi-reference — public API docs\nconfiguration — env vars, config files\ndevelopment — local dev setup, contributing workflow\ndeployment — how to deploy/release\ncontributing — contribution guidelines\nlicense — license info\n</section_catalog>\n\n<rules>\n- Only recommend sections that have real supporting content in the repository.\n- Recommend at most 2 diagrams. Choose diagram types based on:\n - c4-context: multi-service architectures with external dependencies\n - sequence: request/response workflows or multi-actor interactions\n - flowchart: data pipelines, build processes, or decision trees\n - state: object lifecycle or state machine logic\n - er: data models with relationships\n- Detect tone from existing docs, comments, and README style (if present).\n- Be conservative: fewer well-chosen sections beat a bloated list.\n</rules>\n\n<repository_context>\nurl: ${repoData.repo_url}\nsummary: ${repoData.summary}\n\n<tree>\n${repoData.tree}\n</tree>\n\n<content>\n${repoData.content.slice(0, 8000)}\n</content>\n</repository_context>\n${existingReadmeBlock}`;\n}\n\n// ============================================================================\n// Phase 2: Generation Helpers\n// ============================================================================\n\nexport const STYLE_GUIDANCE: Record<DocumentationStyle, string> = {\n\tcomprehensive:\n\t\t\"Be thorough. Multiple examples, edge cases, full API surface. Expand every recommended section fully.\",\n\tminimal:\n\t\t\"Keep it concise. Favor short sentences and small sections. Omit exhaustive examples. Badge count ≤ 3.\",\n\tstandard:\n\t\t\"Balance depth and brevity. One code example per major section. Moderate badge set. Include a quick-start.\",\n};\n\nexport function buildExistingReadmeInstructions(\n\tanalysis: AnalysisResult,\n): string {\n\tif (analysis.existingReadme.quality === \"none\") {\n\t\treturn \"No existing README was found. Generate the document from scratch.\";\n\t}\n\n\tconst lines: string[] = [\n\t\t`Existing README quality: ${analysis.existingReadme.quality}.`,\n\t];\n\n\tif (analysis.existingReadme.preserve.length > 0) {\n\t\tlines.push(\n\t\t\t`Preserve the following content verbatim where possible:\\n${analysis.existingReadme.preserve.map((s) => ` - ${s}`).join(\"\\n\")}`,\n\t\t);\n\t}\n\n\tif (analysis.existingReadme.improve.length > 0) {\n\t\tlines.push(\n\t\t\t`Improve (rewrite/expand) these sections:\\n${analysis.existingReadme.improve.map((s) => ` - ${s}`).join(\"\\n\")}`,\n\t\t);\n\t}\n\n\tif (analysis.existingReadme.missing.length > 0) {\n\t\tlines.push(\n\t\t\t`Add these missing topics:\\n${analysis.existingReadme.missing.map((s) => ` - ${s}`).join(\"\\n\")}`,\n\t\t);\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\n// ============================================================================\n// Phase 2: Generation Prompt\n// ============================================================================\n\nexport function buildGenerationPrompt(\n\tanalysis: AnalysisResult,\n\trepoData: RepoData,\n\toptions: GenerationOptions,\n): string {\n\tconst effectiveTone: Tone = options.tone ?? analysis.tone.detected;\n\n\tconst toneDescriptions: Record<Tone, string> = {\n\t\tcasual:\n\t\t\t\"Friendly, approachable, first-person plural ('we'). Use contractions. Short paragraphs. Light humor is fine.\",\n\t\tminimal:\n\t\t\t\"Extremely terse. Bullet points over prose. No introductory fluff. Every word must earn its place.\",\n\t\tprofessional:\n\t\t\t\"Clear, precise, authoritative. Third-person or imperative. No jargon without definition. Avoid filler words.\",\n\t\ttechnical:\n\t\t\t\"Assume a developer audience. Use correct terminology. Include type signatures, flags, and internals where relevant.\",\n\t};\n\n\treturn `You are a senior technical writer generating a README for a software project.\n\n<output_rules>\n- Output ONLY the raw ${options.format.toUpperCase()} content. No preamble, no explanation, no markdown fences wrapping the whole document.\n- Only generate the sections listed in recommended_sections below. Do not add extras.\n- No generic filler phrases like \"In this section we will…\" or \"This project is a…\".\n- Mermaid diagrams must follow diagram_rules exactly.\n</output_rules>\n\n<analysis>\n${JSON.stringify(analysis, null, 2)}\n</analysis>\n\n<recommended_sections>\n${analysis.sections.recommended.join(\", \")}\n</recommended_sections>\n\n<tone>\nStyle: ${effectiveTone}\nDescription: ${toneDescriptions[effectiveTone]}\n</tone>\n\n<diagram_rules>\nCRITICAL SYNTAX RULES (violations cause render errors):\n- Wrap ALL node labels in quotes if they contain spaces, special chars, or parentheses: A[\"My Label\"]\n- NEVER use bare parentheses in labels — use quotes: A[\"Deploy (prod)\"] NOT A[Deploy (prod)]\n- NEVER use colons in labels without quotes: A[\"Step: Init\"] NOT A[Step: Init]\n- Arrow syntax: use --> for solid, -.-> for dotted. NEVER use -> alone in flowcharts.\n- Subgraph IDs must not contain spaces: subgraph auth_flow[\"Auth Flow\"]\n- Semicolons are NOT terminators — do not end lines with ;\n- Each node definition must be on its own line.\n\nSTYLE RULES:\n- Use descriptive labels on all nodes — never single letters like A, B, C.\n- Add meaningful labels on edges to describe data flow or actions.\n- Use subgraphs to group related nodes into logical clusters.\n- Limit diagrams to 15 nodes maximum.\n\nTYPE-SPECIFIC TEMPLATES (follow these exactly):\n\nflowchart (always TD):\n\\`\\`\\`mermaid\nflowchart TD\n A[\"User Request\"] --> B{\"Auth Valid?\"}\n B -->|Yes| C[\"Process Request\"]\n B -->|No| D[\"Return 401\"]\n C --> E[\"Send Response\"]\n\\`\\`\\`\n\nsequence:\n\\`\\`\\`mermaid\nsequenceDiagram\n actor User\n participant API\n participant DB\n User->>API: POST /login\n activate API\n API->>DB: Query user\n DB-->>API: User record\n API-->>User: JWT token\n deactivate API\n\\`\\`\\`\n\nc4-context (use C4Context):\n\\`\\`\\`mermaid\nC4Context\n Person(user, \"Developer\", \"Uses the CLI tool\")\n System(cli, \"CLI\", \"Command-line interface\")\n System_Ext(api, \"GitHub API\", \"Repository management\")\n Rel(user, cli, \"Runs commands\")\n Rel(cli, api, \"REST calls\")\n\\`\\`\\`\n\ner:\n\\`\\`\\`mermaid\nerDiagram\n USER ||--o{ ORDER : places\n ORDER ||--|{ LINE_ITEM : contains\n PRODUCT ||--o{ LINE_ITEM : \"ordered in\"\n\\`\\`\\`\n\nstate:\n\\`\\`\\`mermaid\nstateDiagram-v2\n [*] --> Idle\n Idle --> Processing : start\n Processing --> Done : complete\n Processing --> Failed : error\n Failed --> Idle : retry\n Done --> [*]\n\\`\\`\\`\n\nCOMMON MISTAKES TO AVOID:\n- Using \"graph\" instead of \"flowchart\"\n- Missing quotes around labels with special characters\n- Using C4_Context instead of C4Context\n- Forgetting \"activate/deactivate\" in sequence diagrams\n- Using --> in sequence diagrams (use ->> or -->> instead)\n</diagram_rules>\n\n<existing_readme_handling>\n${buildExistingReadmeInstructions(analysis)}\n</existing_readme_handling>\n\n<style_profile>\nDocumentation style: ${options.style}\n${STYLE_GUIDANCE[options.style]}\n</style_profile>\n\n<repository_context>\nurl: ${repoData.repo_url}\nsummary: ${repoData.summary}\n\n<tree>\n${repoData.tree}\n</tree>\n\n<content>\n${repoData.content}\n</content>\n</repository_context>`;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"readme.js","names":["Logger"],"sources":["../../src/prompts/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 { Schema } from \"effect\";\n\nimport type { DocumentationStyle } from \"../config.js\";\nimport { Logger } from \"../utils/logger.js\";\n\n// ============================================================================\n// Types & Schemas\n// ============================================================================\n\nexport const DiagramSchema = Schema.Struct({\n\tkeyNodes: Schema.Array(Schema.String),\n\tpurpose: Schema.String,\n\tscope: Schema.String,\n\ttype: Schema.Literal(\"c4-context\", \"er\", \"flowchart\", \"sequence\", \"state\"),\n});\n\nexport const ExistingReadmeSchema = Schema.Struct({\n\timprove: Schema.Array(Schema.String),\n\tmissing: Schema.Array(Schema.String),\n\tpreserve: Schema.Array(Schema.String),\n\tquality: Schema.Literal(\"decent\", \"good\", \"none\", \"poor\"),\n});\n\nexport const IdentitySchema = Schema.Struct({\n\tdifferentiators: Schema.Array(Schema.String),\n\tname: Schema.String,\n\toneLiner: Schema.String,\n});\n\nexport const SectionsSchema = Schema.Struct({\n\trecommended: Schema.Array(Schema.String),\n\tskip: Schema.Array(\n\t\tSchema.Struct({\n\t\t\treason: Schema.String,\n\t\t\tsection: Schema.String,\n\t\t}),\n\t),\n});\n\nexport const ToneSchema = Schema.Literal(\n\t\"casual\",\n\t\"minimal\",\n\t\"professional\",\n\t\"technical\",\n);\nexport type Tone = Schema.Schema.Type<typeof ToneSchema>;\n\nexport const ToneDetectionSchema = Schema.Struct({\n\tdetected: ToneSchema,\n\tevidence: Schema.String,\n});\n\nexport const AnalysisResult = Schema.Struct({\n\tdiagrams: Schema.Array(DiagramSchema),\n\texistingReadme: ExistingReadmeSchema,\n\tidentity: IdentitySchema,\n\trepoType: Schema.String,\n\tsections: SectionsSchema,\n\ttone: ToneDetectionSchema,\n});\nexport type AnalysisResult = Schema.Schema.Type<typeof AnalysisResult>;\n\nexport const GenerationOptions = Schema.Struct({\n\tformat: Schema.Literal(\"md\", \"mdx\"),\n\tstyle: Schema.Literal(\"minimal\", \"standard\", \"comprehensive\"),\n\ttone: Schema.optional(ToneSchema),\n});\nexport type GenerationOptions = Schema.Schema.Type<typeof GenerationOptions>;\n\n// ============================================================================\n// Repo Data Schema\n// ============================================================================\n\nexport const RepoData = Schema.Struct({\n\tcontent: Schema.String,\n\trepo_url: Schema.String,\n\tsummary: Schema.String,\n\ttree: Schema.String,\n});\nexport type RepoData = Schema.Schema.Type<typeof RepoData>;\n\n// ============================================================================\n// Default Fallback\n// ============================================================================\n\nexport const DEFAULT_ANALYSIS: AnalysisResult = {\n\tdiagrams: [],\n\texistingReadme: {\n\t\timprove: [],\n\t\tmissing: [],\n\t\tpreserve: [],\n\t\tquality: \"none\",\n\t},\n\tidentity: {\n\t\tdifferentiators: [],\n\t\tname: \"Unknown\",\n\t\toneLiner: \"\",\n\t},\n\trepoType: \"library\",\n\tsections: {\n\t\trecommended: [\n\t\t\t\"hero\",\n\t\t\t\"overview\",\n\t\t\t\"installation\",\n\t\t\t\"quick-start\",\n\t\t\t\"usage\",\n\t\t\t\"development\",\n\t\t\t\"license\",\n\t\t],\n\t\tskip: [],\n\t},\n\ttone: {\n\t\tdetected: \"professional\",\n\t\tevidence: \"fallback default\",\n\t},\n};\n\n// ============================================================================\n// Utilities\n// ============================================================================\n\nexport function extractExistingReadme(content: string): string | null {\n\tconst readmeMatch =\n\t\t/(?:^|\\n)(?:={3,}|─{3,})?\\s*README\\.md\\s*(?:={3,}|─{3,})?\\n([\\s\\S]*?)(?=\\n(?:={3,}|─{3,})\\s*\\S+\\.\\S+|$)/.exec(\n\t\t\tcontent,\n\t\t);\n\tif (readmeMatch?.[1] && readmeMatch[1].trim().length > 50) {\n\t\treturn readmeMatch[1].trim();\n\t}\n\treturn null;\n}\n\nexport function parseAnalysis(raw: string): AnalysisResult {\n\ttry {\n\t\tconst stripped = raw\n\t\t\t.replace(/^```(?:json)?\\s*/i, \"\")\n\t\t\t.replace(/\\s*```\\s*$/, \"\")\n\t\t\t.trim();\n\n\t\tconst json = JSON.parse(stripped) as unknown;\n\t\tconst result = Schema.decodeUnknownSync(AnalysisResult)(json);\n\n\t\tif (!result.sections.recommended.length) {\n\t\t\tthrow new Error(\"sections.recommended must not be empty\");\n\t\t}\n\n\t\treturn result;\n\t} catch (err) {\n\t\tconst logger = new Logger(\"GREPO:README-PROMPT\");\n\t\tlogger.warn(\n\t\t\t`Failed to parse analysis JSON, falling back to DEFAULT_ANALYSIS`,\n\t\t\t{\n\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\trawLength: raw.length,\n\t\t\t\trawPreview: raw.slice(0, 200),\n\t\t\t},\n\t\t);\n\t\treturn DEFAULT_ANALYSIS;\n\t}\n}\n\n// ============================================================================\n// Phase 1: Analysis Prompt\n// ============================================================================\n\nexport function buildAnalysisPrompt(\n\trepoData: RepoData,\n\texistingReadme?: string,\n): string {\n\tconst existingReadmeBlock = existingReadme\n\t\t? `\n<existing_readme>\n${existingReadme.slice(0, 4000)}\n</existing_readme>`\n\t\t: \"\";\n\n\treturn `You are an expert technical analyst. Analyze the repository below and return ONLY valid JSON — no markdown fences, no commentary, no extra text.\n\n<output_schema>\n{\n \"repoType\": \"string — e.g. cli-tool, library, web-app, api, monorepo, config, data-pipeline, etc.\",\n \"identity\": {\n \"name\": \"string — canonical project name\",\n \"oneLiner\": \"string — one sentence describing what it does\",\n \"differentiators\": [\"string — unique selling points\"]\n },\n \"tone\": {\n \"detected\": \"casual | professional | minimal | technical\",\n \"evidence\": \"string — quote or observation that led to this conclusion\"\n },\n \"sections\": {\n \"recommended\": [\"string — section keys from the catalog below, in logical order\"],\n \"skip\": [{ \"section\": \"string\", \"reason\": \"string\" }]\n },\n \"diagrams\": [\n {\n \"type\": \"flowchart | sequence | c4-context | er | state\",\n \"purpose\": \"string — what this diagram explains\",\n \"scope\": \"string — what subsystem or flow it covers\",\n \"keyNodes\": [\"string — important nodes or actors\"]\n }\n ],\n \"existingReadme\": {\n \"quality\": \"good | decent | poor | none\",\n \"preserve\": [\"string — sections or content worth keeping verbatim\"],\n \"improve\": [\"string — sections that exist but need rework\"],\n \"missing\": [\"string — important topics not covered\"]\n }\n}\n</output_schema>\n\n<section_catalog>\nhero — project name, tagline, badges\noverview — what it does and why it exists\nfeatures — bullet list of capabilities\narchitecture — system design, component overview\ninstallation — how to install/setup\nquick-start — minimal working example\nusage — detailed usage instructions\ncli-reference — CLI flags and subcommands\napi-reference — public API docs\nconfiguration — env vars, config files\ndevelopment — local dev setup, contributing workflow\ndeployment — how to deploy/release\ncontributing — contribution guidelines\nlicense — license info\n</section_catalog>\n\n<rules>\n- Only recommend sections that have real supporting content in the repository.\n- Recommend at most 2 diagrams. Choose diagram types based on:\n - c4-context: multi-service architectures with external dependencies\n - sequence: request/response workflows or multi-actor interactions\n - flowchart: data pipelines, build processes, or decision trees\n - state: object lifecycle or state machine logic\n - er: data models with relationships\n- Detect tone from existing docs, comments, and README style (if present).\n- Be conservative: fewer well-chosen sections beat a bloated list.\n</rules>\n\n<repository_context>\nurl: ${repoData.repo_url}\nsummary: ${repoData.summary}\n\n<tree>\n${repoData.tree}\n</tree>\n\n<content>\n${repoData.content.slice(0, 8000)}\n</content>\n</repository_context>\n${existingReadmeBlock}`;\n}\n\n// ============================================================================\n// Phase 2: Generation Helpers\n// ============================================================================\n\nexport const STYLE_GUIDANCE: Record<DocumentationStyle, string> = {\n\tcomprehensive:\n\t\t\"Be thorough. Multiple examples, edge cases, full API surface. Expand every recommended section fully.\",\n\tminimal:\n\t\t\"Keep it concise. Favor short sentences and small sections. Omit exhaustive examples. Badge count ≤ 3.\",\n\tstandard:\n\t\t\"Balance depth and brevity. One code example per major section. Moderate badge set. Include a quick-start.\",\n};\n\nexport function buildExistingReadmeInstructions(\n\tanalysis: AnalysisResult,\n): string {\n\tif (analysis.existingReadme.quality === \"none\") {\n\t\treturn \"No existing README was found. Generate the document from scratch.\";\n\t}\n\n\tconst lines: string[] = [\n\t\t`Existing README quality: ${analysis.existingReadme.quality}.`,\n\t];\n\n\tif (analysis.existingReadme.preserve.length > 0) {\n\t\tlines.push(\n\t\t\t`Preserve the following content verbatim where possible:\\n${analysis.existingReadme.preserve.map((s) => ` - ${s}`).join(\"\\n\")}`,\n\t\t);\n\t}\n\n\tif (analysis.existingReadme.improve.length > 0) {\n\t\tlines.push(\n\t\t\t`Improve (rewrite/expand) these sections:\\n${analysis.existingReadme.improve.map((s) => ` - ${s}`).join(\"\\n\")}`,\n\t\t);\n\t}\n\n\tif (analysis.existingReadme.missing.length > 0) {\n\t\tlines.push(\n\t\t\t`Add these missing topics:\\n${analysis.existingReadme.missing.map((s) => ` - ${s}`).join(\"\\n\")}`,\n\t\t);\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\n// ============================================================================\n// Phase 2: Generation Prompt\n// ============================================================================\n\nexport function buildGenerationPrompt(\n\tanalysis: AnalysisResult,\n\trepoData: RepoData,\n\toptions: GenerationOptions,\n): string {\n\tconst effectiveTone: Tone = options.tone ?? analysis.tone.detected;\n\n\tconst toneDescriptions: Record<Tone, string> = {\n\t\tcasual:\n\t\t\t\"Friendly, approachable, first-person plural ('we'). Use contractions. Short paragraphs. Light humor is fine.\",\n\t\tminimal:\n\t\t\t\"Extremely terse. Bullet points over prose. No introductory fluff. Every word must earn its place.\",\n\t\tprofessional:\n\t\t\t\"Clear, precise, authoritative. Third-person or imperative. No jargon without definition. Avoid filler words.\",\n\t\ttechnical:\n\t\t\t\"Assume a developer audience. Use correct terminology. Include type signatures, flags, and internals where relevant.\",\n\t};\n\n\treturn `You are a senior technical writer generating a README for a software project.\n\n<output_rules>\n- Output ONLY the raw ${options.format.toUpperCase()} content. No preamble, no explanation, no markdown fences wrapping the whole document.\n- Only generate the sections listed in recommended_sections below. Do not add extras.\n- No generic filler phrases like \"In this section we will…\" or \"This project is a…\".\n- Mermaid diagrams must follow diagram_rules exactly.\n</output_rules>\n\n<analysis>\n${JSON.stringify(analysis, null, 2)}\n</analysis>\n\n<recommended_sections>\n${analysis.sections.recommended.join(\", \")}\n</recommended_sections>\n\n<tone>\nStyle: ${effectiveTone}\nDescription: ${toneDescriptions[effectiveTone]}\n</tone>\n\n<diagram_rules>\nCRITICAL SYNTAX RULES (violations cause render errors):\n- Wrap ALL node labels in quotes if they contain spaces, special chars, or parentheses: A[\"My Label\"]\n- NEVER use bare parentheses in labels — use quotes: A[\"Deploy (prod)\"] NOT A[Deploy (prod)]\n- NEVER use colons in labels without quotes: A[\"Step: Init\"] NOT A[Step: Init]\n- Arrow syntax: use --> for solid, -.-> for dotted. NEVER use -> alone in flowcharts.\n- Subgraph IDs must not contain spaces: subgraph auth_flow[\"Auth Flow\"]\n- Semicolons are NOT terminators — do not end lines with ;\n- Each node definition must be on its own line.\n\nSTYLE RULES:\n- Use descriptive labels on all nodes — never single letters like A, B, C.\n- Add meaningful labels on edges to describe data flow or actions.\n- Use subgraphs to group related nodes into logical clusters.\n- Limit diagrams to 15 nodes maximum.\n\nTYPE-SPECIFIC TEMPLATES (follow these exactly):\n\nflowchart (always TD):\n\\`\\`\\`mermaid\nflowchart TD\n A[\"User Request\"] --> B{\"Auth Valid?\"}\n B -->|Yes| C[\"Process Request\"]\n B -->|No| D[\"Return 401\"]\n C --> E[\"Send Response\"]\n\\`\\`\\`\n\nsequence:\n\\`\\`\\`mermaid\nsequenceDiagram\n actor User\n participant API\n participant DB\n User->>API: POST /login\n activate API\n API->>DB: Query user\n DB-->>API: User record\n API-->>User: JWT token\n deactivate API\n\\`\\`\\`\n\nc4-context (use C4Context):\n\\`\\`\\`mermaid\nC4Context\n Person(user, \"Developer\", \"Uses the CLI tool\")\n System(cli, \"CLI\", \"Command-line interface\")\n System_Ext(api, \"GitHub API\", \"Repository management\")\n Rel(user, cli, \"Runs commands\")\n Rel(cli, api, \"REST calls\")\n\\`\\`\\`\n\ner:\n\\`\\`\\`mermaid\nerDiagram\n USER ||--o{ ORDER : places\n ORDER ||--|{ LINE_ITEM : contains\n PRODUCT ||--o{ LINE_ITEM : \"ordered in\"\n\\`\\`\\`\n\nstate:\n\\`\\`\\`mermaid\nstateDiagram-v2\n [*] --> Idle\n Idle --> Processing : start\n Processing --> Done : complete\n Processing --> Failed : error\n Failed --> Idle : retry\n Done --> [*]\n\\`\\`\\`\n\nCOMMON MISTAKES TO AVOID:\n- Using \"graph\" instead of \"flowchart\"\n- Missing quotes around labels with special characters\n- Using C4_Context instead of C4Context\n- Forgetting \"activate/deactivate\" in sequence diagrams\n- Using --> in sequence diagrams (use ->> or -->> instead)\n</diagram_rules>\n\n<existing_readme_handling>\n${buildExistingReadmeInstructions(analysis)}\n</existing_readme_handling>\n\n<style_profile>\nDocumentation style: ${options.style}\n${STYLE_GUIDANCE[options.style]}\n</style_profile>\n\n<repository_context>\nurl: ${repoData.repo_url}\nsummary: ${repoData.summary}\n\n<tree>\n${repoData.tree}\n</tree>\n\n<content>\n${repoData.content}\n</content>\n</repository_context>`;\n}\n"],"mappings":"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"}
package/lib/services.d.ts CHANGED
@@ -24,7 +24,7 @@ declare const GitHub_base: Context.TagClass<GitHub, "GitHub", GitHubServiceApi>;
24
24
  declare class GitHub extends GitHub_base {}
25
25
  declare const GitHubLive: (token?: string) => Layer.Layer<GitHub, never, never>;
26
26
  type RepoData = Schema.Schema.Type<typeof GitIngestResponse>;
27
- declare const fetchRepo: (repoUrl: string) => Effect.Effect<RepoData, GitIngestError>;
27
+ declare const fetchRepo: (repoUrl: string, token?: string) => Effect.Effect<RepoData, GitIngestError>;
28
28
  //#endregion
29
29
  export { Gemini, GeminiLive, GeminiServiceApi, GitHub, GitHubLive, GitHubServiceApi, RepoData, fetchRepo };
30
30
  //# sourceMappingURL=services.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"services.d.ts","names":[],"sources":["../src/services.ts"],"sourcesContent":[],"mappings":";;;;;;AA+EM,UA9CW,gBAAA,CA8CJ;EAKa,SAAA,eAAA,EAAA,CAAA,MAAA,EAAA,MAAA,EAAA,GAhDpB,MAAA,CAAO,MAgDa,CAAA,MAAA,EAhDE,WAgDF,CAAA;;cA/CzB,WA+CkB,kBAAA,OAAA,EAAA,QAAA,kBAAA,CAAA;AAClB,cA9CY,MAAA,SAAe,WAAA,CA8C3B;cA5CY,gCAA4B,KAAA,CAAA,MAAA;UAiBxB,gBAAA;8DAIX,MAAA,CAAO,eAAe;EAyBf,SAAO,SAAA,EAAA,CAAQ,KAAA,EAAA,MAAiD,EAAA,IAAA,EAAA,MAAA,EAAA,GArBvE,MAAA,CAAO,MAqBgE,CAAA,SAAA,MAAA,EAAA,EArBtC,WAqBsC,CAAA;EAEhE,SAAA,QAiDZ,EAAA,CAAA,KAjDwC,EAAA,MAAA,EAAA,IAAA,EAAA,MAAA,EAAA,IAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,EAAA,MAAA,EAAA,MAAA,EAAA,GAfnC,MAAA,CAAO,MAe4B,CAAA,IAAA,EAff,WAee,CAAA;EAuD7B,SAAA,SAAQ,EAAA,CAAA,KAA6B,EAAA,MAAA,EAAA,IAAA,EAA1B,MAAA,EAAO,MAAO,EAAA,MAAI,EAAA,EAAA,GAjEnC,MAAA,CAAO,MAiE4B,CAAA,IAAA,EAjEf,WAiEe,CAAA;EAE5B,SAAA,UAUV,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,IAAA,EAAA,MAAA,EAAA,IAAA,EAAA;IARc,WAAA,CAAA,EAAA,MAAA;IAAU,QAAA,CAAA,EAAA,MAAA;EAAxB,CAAA,EAAA,GAhEG,MAAA,CAAO,MAgEH,CAAA,IAAA,EAhEgB,WAgEhB,CAAA;;cA/DT;cAEY,MAAA,SAAe,WAAA;cAEf,gCAA4B,KAAA,CAAA,MAAA;KAuD7B,QAAA,GAAW,MAAA,CAAO,MAAA,CAAO,YAAY;cAEpC,gCAEV,MAAA,CAAO,OAAO,UAAU"}
1
+ {"version":3,"file":"services.d.ts","names":[],"sources":["../src/services.ts"],"sourcesContent":[],"mappings":";;;;;;AA+EM,UA9CW,gBAAA,CA8CJ;EAKa,SAAA,eAAA,EAAA,CAAA,MAAA,EAAA,MAAA,EAAA,GAhDpB,MAAA,CAAO,MAgDa,CAAA,MAAA,EAhDE,WAgDF,CAAA;;cA/CzB,WA+CkB,kBAAA,OAAA,EAAA,QAAA,kBAAA,CAAA;AAClB,cA9CY,MAAA,SAAe,WAAA,CA8C3B;cA5CY,gCAA4B,KAAA,CAAA,MAAA;UAiBxB,gBAAA;8DAIX,MAAA,CAAO,eAAe;EAyBf,SAAO,SAAA,EAAA,CAAQ,KAAA,EAAA,MAAiD,EAAA,IAAA,EAAA,MAAA,EAAA,GArBvE,MAAA,CAAO,MAqBgE,CAAA,SAAA,MAAA,EAAA,EArBtC,WAqBsC,CAAA;EAEhE,SAAA,QAiDZ,EAAA,CAAA,KAjDwC,EAAA,MAAA,EAAA,IAAA,EAAA,MAAA,EAAA,IAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,EAAA,MAAA,EAAA,MAAA,EAAA,GAfnC,MAAA,CAAO,MAe4B,CAAA,IAAA,EAff,WAee,CAAA;EAuD7B,SAAA,SAAQ,EAAA,CAAA,KAA6B,EAAA,MAAA,EAAA,IAAA,EAA1B,MAAA,EAAO,MAAO,EAAA,MAAI,EAAA,EAAA,GAjEnC,MAAA,CAAO,MAiE4B,CAAA,IAAA,EAjEf,WAiEe,CAAA;EAE5B,SAAA,UAWV,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,IAAA,EAAA,MAAA,EAAA,IAAA,EAAA;IARc,WAAA,CAAA,EAAA,MAAA;IAAU,QAAA,CAAA,EAAA,MAAA;EAAxB,CAAA,EAAA,GAjEG,MAAA,CAAO,MAiEH,CAAA,IAAA,EAjEgB,WAiEhB,CAAA;;cAhET;cAEY,MAAA,SAAe,WAAA;cAEf,gCAA4B,KAAA,CAAA,MAAA;KAuD7B,QAAA,GAAW,MAAA,CAAO,MAAA,CAAO,YAAY;cAEpC,gDAGV,MAAA,CAAO,OAAO,UAAU"}
package/lib/services.js CHANGED
@@ -53,12 +53,12 @@ const GitHubLive = (token) => {
53
53
  })
54
54
  });
55
55
  };
56
- const fetchRepo = (repoUrl) => Effect.tryPromise({
56
+ const fetchRepo = (repoUrl, token) => Effect.tryPromise({
57
57
  catch: (error) => new GitIngestError({
58
58
  cause: error,
59
59
  message: error instanceof Error ? error.message : String(error)
60
60
  }),
61
- try: () => fetchRepositoryContent(repoUrl)
61
+ try: () => fetchRepositoryContent(repoUrl, token)
62
62
  });
63
63
  //#endregion
64
64
  export { Gemini, GeminiLive, GitHub, GitHubLive, fetchRepo };
@@ -1 +1 @@
1
- {"version":3,"file":"services.js","names":["GeminiClient"],"sources":["../src/services.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 type { Schema } from \"effect\";\nimport { Context, Effect, Layer } from \"effect\";\n\nimport { GeminiError, GitHubError, GitIngestError } from \"./errors.js\";\nimport { GeminiService as GeminiClient } from \"./utils/gemini.js\";\nimport { GitHubClient } from \"./utils/github.js\";\nimport {\n\tfetchRepositoryContent,\n\ttype GitIngestResponse,\n} from \"./utils/gitingest.js\";\n\n// ============================================================================\n// Gemini Service\n// ============================================================================\n\nexport interface GeminiServiceApi {\n\treadonly generateContent: (\n\t\tprompt: string,\n\t) => Effect.Effect<string, GeminiError>;\n}\n\nexport class Gemini extends Context.Tag(\"Gemini\")<Gemini, GeminiServiceApi>() {}\n\nexport const GeminiLive = (apiKey: string) =>\n\tLayer.succeed(Gemini, {\n\t\tgenerateContent: (prompt: string) =>\n\t\t\tEffect.tryPromise({\n\t\t\t\tcatch: (error) =>\n\t\t\t\t\tnew GeminiError({\n\t\t\t\t\t\tcause: error,\n\t\t\t\t\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t}),\n\t\t\t\ttry: () => new GeminiClient(apiKey).generateContent(prompt),\n\t\t\t}),\n\t});\n\n// ============================================================================\n// GitHub Service\n// ============================================================================\n\nexport interface GitHubServiceApi {\n\treadonly getDefaultBranch: (\n\t\towner: string,\n\t\trepo: string,\n\t) => Effect.Effect<string, GitHubError>;\n\treadonly getTopics: (\n\t\towner: string,\n\t\trepo: string,\n\t) => Effect.Effect<readonly string[], GitHubError>;\n\treadonly pushFile: (\n\t\towner: string,\n\t\trepo: string,\n\t\tpath: string,\n\t\tcontent: string,\n\t\tmessage: string,\n\t\tbranch: string,\n\t) => Effect.Effect<void, GitHubError>;\n\treadonly setTopics: (\n\t\towner: string,\n\t\trepo: string,\n\t\ttopics: string[],\n\t) => Effect.Effect<void, GitHubError>;\n\treadonly updateRepo: (\n\t\towner: string,\n\t\trepo: string,\n\t\tdata: { description?: string; homepage?: string },\n\t) => Effect.Effect<void, GitHubError>;\n}\n\nexport class GitHub extends Context.Tag(\"GitHub\")<GitHub, GitHubServiceApi>() {}\n\nexport const GitHubLive = (token?: string) => {\n\tconst client = new GitHubClient(token);\n\treturn Layer.succeed(GitHub, {\n\t\tgetDefaultBranch: (owner, repo) =>\n\t\t\tEffect.tryPromise({\n\t\t\t\tcatch: (error) =>\n\t\t\t\t\tnew GitHubError({\n\t\t\t\t\t\tendpoint: `repos/${owner}/${repo}`,\n\t\t\t\t\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t}),\n\t\t\t\ttry: () => client.getDefaultBranch(owner, repo),\n\t\t\t}),\n\t\tgetTopics: (owner, repo) =>\n\t\t\tEffect.tryPromise({\n\t\t\t\tcatch: (error) =>\n\t\t\t\t\tnew GitHubError({\n\t\t\t\t\t\tendpoint: `repos/${owner}/${repo}/topics`,\n\t\t\t\t\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t}),\n\t\t\t\ttry: () => client.getTopics(owner, repo),\n\t\t\t}),\n\t\tpushFile: (owner, repo, path, content, message, branch) =>\n\t\t\tEffect.tryPromise({\n\t\t\t\tcatch: (error) =>\n\t\t\t\t\tnew GitHubError({\n\t\t\t\t\t\tendpoint: `repos/${owner}/${repo}/contents/${path}`,\n\t\t\t\t\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t}),\n\t\t\t\ttry: () => client.pushFile(owner, repo, path, content, message, branch),\n\t\t\t}),\n\t\tsetTopics: (owner, repo, topics) =>\n\t\t\tEffect.tryPromise({\n\t\t\t\tcatch: (error) =>\n\t\t\t\t\tnew GitHubError({\n\t\t\t\t\t\tendpoint: `repos/${owner}/${repo}/topics`,\n\t\t\t\t\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t}),\n\t\t\t\ttry: () => client.setTopics(owner, repo, topics),\n\t\t\t}),\n\t\tupdateRepo: (owner, repo, data) =>\n\t\t\tEffect.tryPromise({\n\t\t\t\tcatch: (error) =>\n\t\t\t\t\tnew GitHubError({\n\t\t\t\t\t\tendpoint: `repos/${owner}/${repo}`,\n\t\t\t\t\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t}),\n\t\t\t\ttry: () => client.updateRepo(owner, repo, data),\n\t\t\t}),\n\t});\n};\n\n// ============================================================================\n// GitIngest Service\n// ============================================================================\n\nexport type RepoData = Schema.Schema.Type<typeof GitIngestResponse>;\n\nexport const fetchRepo = (\n\trepoUrl: string,\n): Effect.Effect<RepoData, GitIngestError> =>\n\tEffect.tryPromise({\n\t\tcatch: (error) =>\n\t\t\tnew GitIngestError({\n\t\t\t\tcause: error,\n\t\t\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t\t}),\n\t\ttry: () => fetchRepositoryContent(repoUrl),\n\t});\n"],"mappings":";;;;;;AAuCA,IAAa,SAAb,cAA4B,QAAQ,IAAI,SAAS,EAA4B,CAAC;AAE9E,MAAa,cAAc,WAC1B,MAAM,QAAQ,QAAQ,EACrB,kBAAkB,WACjB,OAAO,WAAW;CACjB,QAAQ,UACP,IAAI,YAAY;EACf,OAAO;EACP,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;EAC/D,CAAC;CACH,WAAW,IAAIA,cAAa,OAAO,CAAC,gBAAgB,OAAO;CAC3D,CAAC,EACH,CAAC;AAmCH,IAAa,SAAb,cAA4B,QAAQ,IAAI,SAAS,EAA4B,CAAC;AAE9E,MAAa,cAAc,UAAmB;CAC7C,MAAM,SAAS,IAAI,aAAa,MAAM;AACtC,QAAO,MAAM,QAAQ,QAAQ;EAC5B,mBAAmB,OAAO,SACzB,OAAO,WAAW;GACjB,QAAQ,UACP,IAAI,YAAY;IACf,UAAU,SAAS,MAAM,GAAG;IAC5B,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;IAC/D,CAAC;GACH,WAAW,OAAO,iBAAiB,OAAO,KAAK;GAC/C,CAAC;EACH,YAAY,OAAO,SAClB,OAAO,WAAW;GACjB,QAAQ,UACP,IAAI,YAAY;IACf,UAAU,SAAS,MAAM,GAAG,KAAK;IACjC,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;IAC/D,CAAC;GACH,WAAW,OAAO,UAAU,OAAO,KAAK;GACxC,CAAC;EACH,WAAW,OAAO,MAAM,MAAM,SAAS,SAAS,WAC/C,OAAO,WAAW;GACjB,QAAQ,UACP,IAAI,YAAY;IACf,UAAU,SAAS,MAAM,GAAG,KAAK,YAAY;IAC7C,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;IAC/D,CAAC;GACH,WAAW,OAAO,SAAS,OAAO,MAAM,MAAM,SAAS,SAAS,OAAO;GACvE,CAAC;EACH,YAAY,OAAO,MAAM,WACxB,OAAO,WAAW;GACjB,QAAQ,UACP,IAAI,YAAY;IACf,UAAU,SAAS,MAAM,GAAG,KAAK;IACjC,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;IAC/D,CAAC;GACH,WAAW,OAAO,UAAU,OAAO,MAAM,OAAO;GAChD,CAAC;EACH,aAAa,OAAO,MAAM,SACzB,OAAO,WAAW;GACjB,QAAQ,UACP,IAAI,YAAY;IACf,UAAU,SAAS,MAAM,GAAG;IAC5B,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;IAC/D,CAAC;GACH,WAAW,OAAO,WAAW,OAAO,MAAM,KAAK;GAC/C,CAAC;EACH,CAAC;;AASH,MAAa,aACZ,YAEA,OAAO,WAAW;CACjB,QAAQ,UACP,IAAI,eAAe;EAClB,OAAO;EACP,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;EAC/D,CAAC;CACH,WAAW,uBAAuB,QAAQ;CAC1C,CAAC"}
1
+ {"version":3,"file":"services.js","names":["GeminiClient"],"sources":["../src/services.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 type { Schema } from \"effect\";\nimport { Context, Effect, Layer } from \"effect\";\n\nimport { GeminiError, GitHubError, GitIngestError } from \"./errors.js\";\nimport { GeminiService as GeminiClient } from \"./utils/gemini.js\";\nimport { GitHubClient } from \"./utils/github.js\";\nimport {\n\tfetchRepositoryContent,\n\ttype GitIngestResponse,\n} from \"./utils/gitingest.js\";\n\n// ============================================================================\n// Gemini Service\n// ============================================================================\n\nexport interface GeminiServiceApi {\n\treadonly generateContent: (\n\t\tprompt: string,\n\t) => Effect.Effect<string, GeminiError>;\n}\n\nexport class Gemini extends Context.Tag(\"Gemini\")<Gemini, GeminiServiceApi>() {}\n\nexport const GeminiLive = (apiKey: string) =>\n\tLayer.succeed(Gemini, {\n\t\tgenerateContent: (prompt: string) =>\n\t\t\tEffect.tryPromise({\n\t\t\t\tcatch: (error) =>\n\t\t\t\t\tnew GeminiError({\n\t\t\t\t\t\tcause: error,\n\t\t\t\t\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t}),\n\t\t\t\ttry: () => new GeminiClient(apiKey).generateContent(prompt),\n\t\t\t}),\n\t});\n\n// ============================================================================\n// GitHub Service\n// ============================================================================\n\nexport interface GitHubServiceApi {\n\treadonly getDefaultBranch: (\n\t\towner: string,\n\t\trepo: string,\n\t) => Effect.Effect<string, GitHubError>;\n\treadonly getTopics: (\n\t\towner: string,\n\t\trepo: string,\n\t) => Effect.Effect<readonly string[], GitHubError>;\n\treadonly pushFile: (\n\t\towner: string,\n\t\trepo: string,\n\t\tpath: string,\n\t\tcontent: string,\n\t\tmessage: string,\n\t\tbranch: string,\n\t) => Effect.Effect<void, GitHubError>;\n\treadonly setTopics: (\n\t\towner: string,\n\t\trepo: string,\n\t\ttopics: string[],\n\t) => Effect.Effect<void, GitHubError>;\n\treadonly updateRepo: (\n\t\towner: string,\n\t\trepo: string,\n\t\tdata: { description?: string; homepage?: string },\n\t) => Effect.Effect<void, GitHubError>;\n}\n\nexport class GitHub extends Context.Tag(\"GitHub\")<GitHub, GitHubServiceApi>() {}\n\nexport const GitHubLive = (token?: string) => {\n\tconst client = new GitHubClient(token);\n\treturn Layer.succeed(GitHub, {\n\t\tgetDefaultBranch: (owner, repo) =>\n\t\t\tEffect.tryPromise({\n\t\t\t\tcatch: (error) =>\n\t\t\t\t\tnew GitHubError({\n\t\t\t\t\t\tendpoint: `repos/${owner}/${repo}`,\n\t\t\t\t\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t}),\n\t\t\t\ttry: () => client.getDefaultBranch(owner, repo),\n\t\t\t}),\n\t\tgetTopics: (owner, repo) =>\n\t\t\tEffect.tryPromise({\n\t\t\t\tcatch: (error) =>\n\t\t\t\t\tnew GitHubError({\n\t\t\t\t\t\tendpoint: `repos/${owner}/${repo}/topics`,\n\t\t\t\t\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t}),\n\t\t\t\ttry: () => client.getTopics(owner, repo),\n\t\t\t}),\n\t\tpushFile: (owner, repo, path, content, message, branch) =>\n\t\t\tEffect.tryPromise({\n\t\t\t\tcatch: (error) =>\n\t\t\t\t\tnew GitHubError({\n\t\t\t\t\t\tendpoint: `repos/${owner}/${repo}/contents/${path}`,\n\t\t\t\t\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t}),\n\t\t\t\ttry: () => client.pushFile(owner, repo, path, content, message, branch),\n\t\t\t}),\n\t\tsetTopics: (owner, repo, topics) =>\n\t\t\tEffect.tryPromise({\n\t\t\t\tcatch: (error) =>\n\t\t\t\t\tnew GitHubError({\n\t\t\t\t\t\tendpoint: `repos/${owner}/${repo}/topics`,\n\t\t\t\t\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t}),\n\t\t\t\ttry: () => client.setTopics(owner, repo, topics),\n\t\t\t}),\n\t\tupdateRepo: (owner, repo, data) =>\n\t\t\tEffect.tryPromise({\n\t\t\t\tcatch: (error) =>\n\t\t\t\t\tnew GitHubError({\n\t\t\t\t\t\tendpoint: `repos/${owner}/${repo}`,\n\t\t\t\t\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t}),\n\t\t\t\ttry: () => client.updateRepo(owner, repo, data),\n\t\t\t}),\n\t});\n};\n\n// ============================================================================\n// GitIngest Service\n// ============================================================================\n\nexport type RepoData = Schema.Schema.Type<typeof GitIngestResponse>;\n\nexport const fetchRepo = (\n\trepoUrl: string,\n\ttoken?: string,\n): Effect.Effect<RepoData, GitIngestError> =>\n\tEffect.tryPromise({\n\t\tcatch: (error) =>\n\t\t\tnew GitIngestError({\n\t\t\t\tcause: error,\n\t\t\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t\t}),\n\t\ttry: () => fetchRepositoryContent(repoUrl, token),\n\t});\n"],"mappings":";;;;;;AAuCA,IAAa,SAAb,cAA4B,QAAQ,IAAI,SAAS,EAA4B,CAAC;AAE9E,MAAa,cAAc,WAC1B,MAAM,QAAQ,QAAQ,EACrB,kBAAkB,WACjB,OAAO,WAAW;CACjB,QAAQ,UACP,IAAI,YAAY;EACf,OAAO;EACP,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;EAC/D,CAAC;CACH,WAAW,IAAIA,cAAa,OAAO,CAAC,gBAAgB,OAAO;CAC3D,CAAC,EACH,CAAC;AAmCH,IAAa,SAAb,cAA4B,QAAQ,IAAI,SAAS,EAA4B,CAAC;AAE9E,MAAa,cAAc,UAAmB;CAC7C,MAAM,SAAS,IAAI,aAAa,MAAM;AACtC,QAAO,MAAM,QAAQ,QAAQ;EAC5B,mBAAmB,OAAO,SACzB,OAAO,WAAW;GACjB,QAAQ,UACP,IAAI,YAAY;IACf,UAAU,SAAS,MAAM,GAAG;IAC5B,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;IAC/D,CAAC;GACH,WAAW,OAAO,iBAAiB,OAAO,KAAK;GAC/C,CAAC;EACH,YAAY,OAAO,SAClB,OAAO,WAAW;GACjB,QAAQ,UACP,IAAI,YAAY;IACf,UAAU,SAAS,MAAM,GAAG,KAAK;IACjC,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;IAC/D,CAAC;GACH,WAAW,OAAO,UAAU,OAAO,KAAK;GACxC,CAAC;EACH,WAAW,OAAO,MAAM,MAAM,SAAS,SAAS,WAC/C,OAAO,WAAW;GACjB,QAAQ,UACP,IAAI,YAAY;IACf,UAAU,SAAS,MAAM,GAAG,KAAK,YAAY;IAC7C,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;IAC/D,CAAC;GACH,WAAW,OAAO,SAAS,OAAO,MAAM,MAAM,SAAS,SAAS,OAAO;GACvE,CAAC;EACH,YAAY,OAAO,MAAM,WACxB,OAAO,WAAW;GACjB,QAAQ,UACP,IAAI,YAAY;IACf,UAAU,SAAS,MAAM,GAAG,KAAK;IACjC,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;IAC/D,CAAC;GACH,WAAW,OAAO,UAAU,OAAO,MAAM,OAAO;GAChD,CAAC;EACH,aAAa,OAAO,MAAM,SACzB,OAAO,WAAW;GACjB,QAAQ,UACP,IAAI,YAAY;IACf,UAAU,SAAS,MAAM,GAAG;IAC5B,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;IAC/D,CAAC;GACH,WAAW,OAAO,WAAW,OAAO,MAAM,KAAK;GAC/C,CAAC;EACH,CAAC;;AASH,MAAa,aACZ,SACA,UAEA,OAAO,WAAW;CACjB,QAAQ,UACP,IAAI,eAAe;EAClB,OAAO;EACP,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;EAC/D,CAAC;CACH,WAAW,uBAAuB,SAAS,MAAM;CACjD,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { Schema } from "effect";
2
+
3
+ //#region src/utils/config-file.d.ts
4
+
5
+ declare const CONFIG_PATH: string;
6
+ declare const ConfigFileSchema: Schema.Struct<{
7
+ geminiApiKey: Schema.optional<typeof Schema.String>;
8
+ githubToken: Schema.optional<typeof Schema.String>;
9
+ }>;
10
+ type ConfigFile = Schema.Schema.Type<typeof ConfigFileSchema>;
11
+ declare function writeConfigFile(config: ConfigFile): void;
12
+ declare function promptConfigSetup(): Promise<ConfigFile>;
13
+ declare function loadConfigFile(): ConfigFile;
14
+ //#endregion
15
+ export { CONFIG_PATH, ConfigFile, ConfigFileSchema, loadConfigFile, promptConfigSetup, writeConfigFile };
16
+ //# sourceMappingURL=config-file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-file.d.ts","names":[],"sources":["../../src/utils/config-file.ts"],"sourcesContent":[],"mappings":";;;;cAkCa;cAEA,kBAAgB,MAAA,CAAA;;;;KAKjB,UAAA,GAAa,MAAA,CAAO,MAAA,CAAO,YAAY;iBAEnC,eAAA,SAAwB;iBAiBlB,iBAAA,CAAA,GAAqB,QAAQ;iBA4BnC,cAAA,CAAA,GAAkB"}
@@ -0,0 +1,80 @@
1
+ import { Logger as Logger$1 } from "./logger.js";
2
+ import { Schema } from "effect";
3
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { homedir } from "node:os";
6
+ import { createInterface } from "node:readline";
7
+ //#region src/utils/config-file.ts
8
+ /**
9
+ *
10
+ * Copyright 2026 Mike Odnis
11
+ *
12
+ * Licensed under the Apache License, Version 2.0 (the "License");
13
+ * you may not use this file except in compliance with the License.
14
+ * You may obtain a copy of the License at
15
+ *
16
+ * http://www.apache.org/licenses/LICENSE-2.0
17
+ *
18
+ * Unless required by applicable law or agreed to in writing, software
19
+ * distributed under the License is distributed on an "AS IS" BASIS,
20
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
+ * See the License for the specific language governing permissions and
22
+ * limitations under the License.
23
+ *
24
+ */
25
+ const logger = Logger$1.getLogger("GREPO:CONFIG");
26
+ const CONFIG_PATH = join(homedir(), ".config", "grepo", "config.json");
27
+ const ConfigFileSchema = Schema.Struct({
28
+ geminiApiKey: Schema.optional(Schema.String),
29
+ githubToken: Schema.optional(Schema.String)
30
+ });
31
+ function writeConfigFile(config) {
32
+ mkdirSync(CONFIG_PATH.replace(/\/[^/]+$/, ""), { recursive: true });
33
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
34
+ chmodSync(CONFIG_PATH, 384);
35
+ logger.info("Config file written", { path: CONFIG_PATH });
36
+ }
37
+ function ask(rl, question) {
38
+ return new Promise((resolve) => {
39
+ rl.question(question, (answer) => {
40
+ resolve(answer);
41
+ });
42
+ });
43
+ }
44
+ async function promptConfigSetup() {
45
+ const rl = createInterface({
46
+ input: process.stdin,
47
+ output: process.stderr
48
+ });
49
+ try {
50
+ logger.info("No API keys found. Let's set up your config.");
51
+ const geminiApiKey = await ask(rl, " Gemini API key: ");
52
+ const githubToken = await ask(rl, " GitHub token (optional, press Enter to skip): ");
53
+ const config = {
54
+ geminiApiKey: geminiApiKey || void 0,
55
+ githubToken: githubToken || void 0
56
+ };
57
+ writeConfigFile(config);
58
+ return config;
59
+ } finally {
60
+ rl.close();
61
+ }
62
+ }
63
+ function loadConfigFile() {
64
+ if (!existsSync(CONFIG_PATH)) return {};
65
+ try {
66
+ const raw = readFileSync(CONFIG_PATH, "utf-8");
67
+ const parsed = JSON.parse(raw);
68
+ return Schema.decodeUnknownSync(ConfigFileSchema)(parsed);
69
+ } catch (error) {
70
+ logger.warn("Failed to read config file", {
71
+ path: CONFIG_PATH,
72
+ error: error instanceof Error ? error.message : String(error)
73
+ });
74
+ return {};
75
+ }
76
+ }
77
+ //#endregion
78
+ export { CONFIG_PATH, ConfigFileSchema, loadConfigFile, promptConfigSetup, writeConfigFile };
79
+
80
+ //# sourceMappingURL=config-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-file.js","names":["Logger"],"sources":["../../src/utils/config-file.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 {\n\tchmodSync,\n\texistsSync,\n\tmkdirSync,\n\treadFileSync,\n\twriteFileSync,\n} from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { createInterface, type Interface } from \"node:readline\";\nimport { Schema } from \"effect\";\n\nimport { Logger } from \"./logger.js\";\n\nconst logger = Logger.getLogger(\"GREPO:CONFIG\");\n\nexport const CONFIG_PATH = join(homedir(), \".config\", \"grepo\", \"config.json\");\n\nexport const ConfigFileSchema = Schema.Struct({\n\tgeminiApiKey: Schema.optional(Schema.String),\n\tgithubToken: Schema.optional(Schema.String),\n});\n\nexport type ConfigFile = Schema.Schema.Type<typeof ConfigFileSchema>;\n\nexport function writeConfigFile(config: ConfigFile): void {\n\tconst dir = CONFIG_PATH.replace(/\\/[^/]+$/, \"\");\n\tmkdirSync(dir, { recursive: true });\n\tconst content = JSON.stringify(config, null, 2);\n\twriteFileSync(CONFIG_PATH, content, \"utf-8\");\n\tchmodSync(CONFIG_PATH, 0o600);\n\tlogger.info(\"Config file written\", { path: CONFIG_PATH });\n}\n\nfunction ask(rl: Interface, question: string): Promise<string> {\n\treturn new Promise((resolve) => {\n\t\trl.question(question, (answer: string) => {\n\t\t\tresolve(answer);\n\t\t});\n\t});\n}\n\nexport async function promptConfigSetup(): Promise<ConfigFile> {\n\tconst rl = createInterface({\n\t\tinput: process.stdin,\n\t\toutput: process.stderr,\n\t});\n\n\ttry {\n\t\tlogger.info(\"No API keys found. Let's set up your config.\");\n\n\t\tconst geminiApiKey = await ask(rl, \" Gemini API key: \");\n\t\tconst githubToken = await ask(\n\t\t\trl,\n\t\t\t\" GitHub token (optional, press Enter to skip): \",\n\t\t);\n\n\t\tconst config: ConfigFile = {\n\t\t\tgeminiApiKey: geminiApiKey || undefined,\n\t\t\tgithubToken: githubToken || undefined,\n\t\t};\n\n\t\twriteConfigFile(config);\n\n\t\treturn config;\n\t} finally {\n\t\trl.close();\n\t}\n}\n\nexport function loadConfigFile(): ConfigFile {\n\tif (!existsSync(CONFIG_PATH)) {\n\t\treturn {};\n\t}\n\n\ttry {\n\t\tconst raw = readFileSync(CONFIG_PATH, \"utf-8\");\n\t\tconst parsed: unknown = JSON.parse(raw);\n\t\treturn Schema.decodeUnknownSync(ConfigFileSchema)(parsed);\n\t} catch (error) {\n\t\tlogger.warn(\"Failed to read config file\", {\n\t\t\tpath: CONFIG_PATH,\n\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t});\n\t\treturn {};\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAgCA,MAAM,SAASA,SAAO,UAAU,eAAe;AAE/C,MAAa,cAAc,KAAK,SAAS,EAAE,WAAW,SAAS,cAAc;AAE7E,MAAa,mBAAmB,OAAO,OAAO;CAC7C,cAAc,OAAO,SAAS,OAAO,OAAO;CAC5C,aAAa,OAAO,SAAS,OAAO,OAAO;CAC3C,CAAC;AAIF,SAAgB,gBAAgB,QAA0B;AAEzD,WADY,YAAY,QAAQ,YAAY,GAAG,EAChC,EAAE,WAAW,MAAM,CAAC;AAEnC,eAAc,aADE,KAAK,UAAU,QAAQ,MAAM,EAAE,EACX,QAAQ;AAC5C,WAAU,aAAa,IAAM;AAC7B,QAAO,KAAK,uBAAuB,EAAE,MAAM,aAAa,CAAC;;AAG1D,SAAS,IAAI,IAAe,UAAmC;AAC9D,QAAO,IAAI,SAAS,YAAY;AAC/B,KAAG,SAAS,WAAW,WAAmB;AACzC,WAAQ,OAAO;IACd;GACD;;AAGH,eAAsB,oBAAyC;CAC9D,MAAM,KAAK,gBAAgB;EAC1B,OAAO,QAAQ;EACf,QAAQ,QAAQ;EAChB,CAAC;AAEF,KAAI;AACH,SAAO,KAAK,+CAA+C;EAE3D,MAAM,eAAe,MAAM,IAAI,IAAI,qBAAqB;EACxD,MAAM,cAAc,MAAM,IACzB,IACA,mDACA;EAED,MAAM,SAAqB;GAC1B,cAAc,gBAAgB,KAAA;GAC9B,aAAa,eAAe,KAAA;GAC5B;AAED,kBAAgB,OAAO;AAEvB,SAAO;WACE;AACT,KAAG,OAAO;;;AAIZ,SAAgB,iBAA6B;AAC5C,KAAI,CAAC,WAAW,YAAY,CAC3B,QAAO,EAAE;AAGV,KAAI;EACH,MAAM,MAAM,aAAa,aAAa,QAAQ;EAC9C,MAAM,SAAkB,KAAK,MAAM,IAAI;AACvC,SAAO,OAAO,kBAAkB,iBAAiB,CAAC,OAAO;UACjD,OAAO;AACf,SAAO,KAAK,8BAA8B;GACzC,MAAM;GACN,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;GAC7D,CAAC;AACF,SAAO,EAAE"}
@@ -1 +1 @@
1
- {"version":3,"file":"gemini.d.ts","names":[],"sources":["../../src/utils/gemini.ts"],"sourcesContent":[],"mappings":";;;;cAwBa,mBAAiB,MAAA,CAAA;;;;;;KAMlB,iBAAA,GAAoB,MAAA,CAAO,MAAA,CAAO,YAAY;cAE7C,aAAA;;;;;;;;;4CAyGF,oBACP"}
1
+ {"version":3,"file":"gemini.d.ts","names":[],"sources":["../../src/utils/gemini.ts"],"sourcesContent":[],"mappings":";;;;cAwBa,mBAAiB,MAAA,CAAA;;;;;;KAMlB,iBAAA,GAAoB,MAAA,CAAO,MAAA,CAAO,YAAY;cAE7C,aAAA;;;;;;;;;4CA0GF,oBACP"}
@@ -1,5 +1,5 @@
1
- import { ApiError } from "./validation.js";
2
1
  import { Logger as Logger$1 } from "./logger.js";
2
+ import { ApiError } from "./validation.js";
3
3
  import { Schema } from "effect";
4
4
  import { GoogleGenAI } from "@google/genai";
5
5
  //#region src/utils/gemini.ts
@@ -45,7 +45,7 @@ var GeminiService = class {
45
45
  return names;
46
46
  } catch (error) {
47
47
  const errorMsg = error instanceof Error ? error.message : String(error);
48
- this.logger.warn(`Failed to fetch models list`, { error: errorMsg });
48
+ this.logger.warn(`Failed to fetch models list, falling back to hardcoded defaults`, { error: errorMsg });
49
49
  return [];
50
50
  }
51
51
  }
@@ -93,12 +93,16 @@ var GeminiService = class {
93
93
  } catch (error) {
94
94
  const nextModel = models[i + 1];
95
95
  if (this.isRateLimitError(error) && nextModel) {
96
- this.logger.warn(`Rate limited on ${modelName}, falling back to ${nextModel}`);
96
+ this.logger.warn(`Rate limited on ${modelName}, falling back to ${nextModel}`, {
97
+ model: modelName,
98
+ fallback: nextModel
99
+ });
97
100
  continue;
98
101
  }
99
102
  throw new ApiError(`Gemini generation failed (${modelName}): ${error instanceof Error ? error.message : "Unknown error"}`, void 0, "Gemini API");
100
103
  }
101
104
  }
105
+ this.logger.warn("All Gemini models exhausted", { modelsAttempted: models.join(", ") });
102
106
  throw new ApiError("All Gemini models exhausted", void 0, "Gemini API");
103
107
  }
104
108
  };
@@ -1 +1 @@
1
- {"version":3,"file":"gemini.js","names":["Logger"],"sources":["../../src/utils/gemini.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 { GoogleGenAI, type Model } from \"@google/genai\";\nimport { Schema } from \"effect\";\n\nimport { Logger } from \"./logger.js\";\nimport { ApiError } from \"./validation.js\";\n\nexport const GenerationOptions = Schema.Struct({\n\tmodel: Schema.optional(Schema.String),\n\ttemperature: Schema.optional(Schema.Number),\n\ttopK: Schema.optional(Schema.Number),\n\ttopP: Schema.optional(Schema.Number),\n});\nexport type GenerationOptions = Schema.Schema.Type<typeof GenerationOptions>;\n\nexport class GeminiService {\n\tprivate readonly genAI: GoogleGenAI;\n\tprivate readonly logger: Logger;\n\n\tconstructor(apiKey: string) {\n\t\tthis.genAI = new GoogleGenAI({ apiKey });\n\t\tthis.logger = new Logger(\"GeminiService\");\n\t}\n\n\tprivate cachedModels: string[] | null = null;\n\n\tprivate async fetchAvailableModels(): Promise<string[]> {\n\t\tif (this.cachedModels) {\n\t\t\treturn this.cachedModels;\n\t\t}\n\n\t\ttry {\n\t\t\tconst modelsPager = await this.genAI.models.list();\n\t\t\tconst allModels: Model[] = [];\n\t\t\tfor await (const model of modelsPager) {\n\t\t\t\tallModels.push(model);\n\t\t\t}\n\n\t\t\tconst names = allModels\n\t\t\t\t.filter(\n\t\t\t\t\t(m) => m.name && m.supportedActions?.includes(\"generateContent\"),\n\t\t\t\t)\n\t\t\t\t.map((m) => (m.name ?? \"\").replace(\"models/\", \"\"));\n\n\t\t\tthis.cachedModels = names;\n\t\t\treturn names;\n\t\t} catch (error) {\n\t\t\tconst errorMsg = error instanceof Error ? error.message : String(error);\n\t\t\tthis.logger.warn(`Failed to fetch models list`, {\n\t\t\t\terror: errorMsg,\n\t\t\t});\n\t\t\treturn [];\n\t\t}\n\t}\n\n\tprivate parseModelVersion(name: string): number {\n\t\tconst match = /(\\d+)\\.(\\d+)/.exec(name);\n\t\tif (!match) {\n\t\t\treturn 0;\n\t\t}\n\t\treturn Number(match[1]) * 1000 + Number(match[2]);\n\t}\n\n\tprivate async getModelFallbackChain(preferred?: string): Promise<string[]> {\n\t\tconst available = await this.fetchAvailableModels();\n\n\t\tconst tierOf = (name: string) => {\n\t\t\tif (name.includes(\"flash-lite\")) {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tif (name.includes(\"flash\")) {\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\tif (name.includes(\"pro\")) {\n\t\t\t\treturn 2;\n\t\t\t}\n\t\t\treturn 3;\n\t\t};\n\n\t\tconst ranked = available\n\t\t\t.filter(\n\t\t\t\t(m) =>\n\t\t\t\t\t!m.includes(\"embedding\") &&\n\t\t\t\t\t!m.includes(\"aqa\") &&\n\t\t\t\t\t!m.includes(\"imagen\") &&\n\t\t\t\t\t!m.includes(\"veo\") &&\n\t\t\t\t\t!m.includes(\"tts\"),\n\t\t\t)\n\t\t\t.sort((a, b) => {\n\t\t\t\tconst tierDiff = tierOf(a) - tierOf(b);\n\t\t\t\tif (tierDiff !== 0) {\n\t\t\t\t\treturn tierDiff;\n\t\t\t\t}\n\t\t\t\treturn this.parseModelVersion(b) - this.parseModelVersion(a);\n\t\t\t});\n\n\t\tif (preferred) {\n\t\t\treturn [preferred, ...ranked.filter((m) => m !== preferred)];\n\t\t}\n\n\t\treturn ranked.length > 0\n\t\t\t? ranked\n\t\t\t: [\"gemini-2.0-flash\", \"gemini-1.5-flash\"];\n\t}\n\n\tprivate isRateLimitError(error: unknown): boolean {\n\t\tif (!(error instanceof Error)) {\n\t\t\treturn false;\n\t\t}\n\t\tconst msg = error.message.toLowerCase();\n\t\treturn (\n\t\t\tmsg.includes(\"429\") ||\n\t\t\tmsg.includes(\"rate limit\") ||\n\t\t\tmsg.includes(\"resource exhausted\") ||\n\t\t\tmsg.includes(\"quota\")\n\t\t);\n\t}\n\n\tasync generateContent(\n\t\tprompt: string,\n\t\toptions: GenerationOptions = {},\n\t): Promise<string> {\n\t\tconst models = await this.getModelFallbackChain(options.model);\n\n\t\tfor (let i = 0; i < models.length; i++) {\n\t\t\tconst modelName = models[i];\n\t\t\tif (!modelName) continue;\n\t\t\ttry {\n\t\t\t\tconst result = await this.genAI.models.generateContent({\n\t\t\t\t\tcontents: [{ parts: [{ text: prompt }], role: \"user\" }],\n\t\t\t\t\tmodel: modelName,\n\t\t\t\t});\n\n\t\t\t\tconst text = result.text;\n\n\t\t\t\tif (!text) {\n\t\t\t\t\tthrow new ApiError(\n\t\t\t\t\t\t\"Gemini returned empty content\",\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\t\"Gemini API\",\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\treturn text;\n\t\t\t} catch (error) {\n\t\t\t\tconst nextModel = models[i + 1];\n\t\t\t\tif (this.isRateLimitError(error) && nextModel) {\n\t\t\t\t\tthis.logger.warn(\n\t\t\t\t\t\t`Rate limited on ${modelName}, falling back to ${nextModel}`,\n\t\t\t\t\t);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst message =\n\t\t\t\t\terror instanceof Error ? error.message : \"Unknown error\";\n\t\t\t\tthrow new ApiError(\n\t\t\t\t\t`Gemini generation failed (${modelName}): ${message}`,\n\t\t\t\t\tundefined,\n\t\t\t\t\t\"Gemini API\",\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tthrow new ApiError(\"All Gemini models exhausted\", undefined, \"Gemini API\");\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"gemini.js","names":["Logger"],"sources":["../../src/utils/gemini.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 { GoogleGenAI, type Model } from \"@google/genai\";\nimport { Schema } from \"effect\";\n\nimport { Logger } from \"./logger.js\";\nimport { ApiError } from \"./validation.js\";\n\nexport const GenerationOptions = Schema.Struct({\n\tmodel: Schema.optional(Schema.String),\n\ttemperature: Schema.optional(Schema.Number),\n\ttopK: Schema.optional(Schema.Number),\n\ttopP: Schema.optional(Schema.Number),\n});\nexport type GenerationOptions = Schema.Schema.Type<typeof GenerationOptions>;\n\nexport class GeminiService {\n\tprivate readonly genAI: GoogleGenAI;\n\tprivate readonly logger: Logger;\n\n\tconstructor(apiKey: string) {\n\t\tthis.genAI = new GoogleGenAI({ apiKey });\n\t\tthis.logger = new Logger(\"GeminiService\");\n\t}\n\n\tprivate cachedModels: string[] | null = null;\n\n\tprivate async fetchAvailableModels(): Promise<string[]> {\n\t\tif (this.cachedModels) {\n\t\t\treturn this.cachedModels;\n\t\t}\n\n\t\ttry {\n\t\t\tconst modelsPager = await this.genAI.models.list();\n\t\t\tconst allModels: Model[] = [];\n\t\t\tfor await (const model of modelsPager) {\n\t\t\t\tallModels.push(model);\n\t\t\t}\n\n\t\t\tconst names = allModels\n\t\t\t\t.filter(\n\t\t\t\t\t(m) => m.name && m.supportedActions?.includes(\"generateContent\"),\n\t\t\t\t)\n\t\t\t\t.map((m) => (m.name ?? \"\").replace(\"models/\", \"\"));\n\n\t\t\tthis.cachedModels = names;\n\t\t\treturn names;\n\t\t} catch (error) {\n\t\t\tconst errorMsg = error instanceof Error ? error.message : String(error);\n\t\t\tthis.logger.warn(\n\t\t\t\t`Failed to fetch models list, falling back to hardcoded defaults`,\n\t\t\t\t{ error: errorMsg },\n\t\t\t);\n\t\t\treturn [];\n\t\t}\n\t}\n\n\tprivate parseModelVersion(name: string): number {\n\t\tconst match = /(\\d+)\\.(\\d+)/.exec(name);\n\t\tif (!match) {\n\t\t\treturn 0;\n\t\t}\n\t\treturn Number(match[1]) * 1000 + Number(match[2]);\n\t}\n\n\tprivate async getModelFallbackChain(preferred?: string): Promise<string[]> {\n\t\tconst available = await this.fetchAvailableModels();\n\n\t\tconst tierOf = (name: string) => {\n\t\t\tif (name.includes(\"flash-lite\")) {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tif (name.includes(\"flash\")) {\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\tif (name.includes(\"pro\")) {\n\t\t\t\treturn 2;\n\t\t\t}\n\t\t\treturn 3;\n\t\t};\n\n\t\tconst ranked = available\n\t\t\t.filter(\n\t\t\t\t(m) =>\n\t\t\t\t\t!m.includes(\"embedding\") &&\n\t\t\t\t\t!m.includes(\"aqa\") &&\n\t\t\t\t\t!m.includes(\"imagen\") &&\n\t\t\t\t\t!m.includes(\"veo\") &&\n\t\t\t\t\t!m.includes(\"tts\"),\n\t\t\t)\n\t\t\t.sort((a, b) => {\n\t\t\t\tconst tierDiff = tierOf(a) - tierOf(b);\n\t\t\t\tif (tierDiff !== 0) {\n\t\t\t\t\treturn tierDiff;\n\t\t\t\t}\n\t\t\t\treturn this.parseModelVersion(b) - this.parseModelVersion(a);\n\t\t\t});\n\n\t\tif (preferred) {\n\t\t\treturn [preferred, ...ranked.filter((m) => m !== preferred)];\n\t\t}\n\n\t\treturn ranked.length > 0\n\t\t\t? ranked\n\t\t\t: [\"gemini-2.0-flash\", \"gemini-1.5-flash\"];\n\t}\n\n\tprivate isRateLimitError(error: unknown): boolean {\n\t\tif (!(error instanceof Error)) {\n\t\t\treturn false;\n\t\t}\n\t\tconst msg = error.message.toLowerCase();\n\t\treturn (\n\t\t\tmsg.includes(\"429\") ||\n\t\t\tmsg.includes(\"rate limit\") ||\n\t\t\tmsg.includes(\"resource exhausted\") ||\n\t\t\tmsg.includes(\"quota\")\n\t\t);\n\t}\n\n\tasync generateContent(\n\t\tprompt: string,\n\t\toptions: GenerationOptions = {},\n\t): Promise<string> {\n\t\tconst models = await this.getModelFallbackChain(options.model);\n\n\t\tfor (let i = 0; i < models.length; i++) {\n\t\t\tconst modelName = models[i];\n\t\t\tif (!modelName) continue;\n\t\t\ttry {\n\t\t\t\tconst result = await this.genAI.models.generateContent({\n\t\t\t\t\tcontents: [{ parts: [{ text: prompt }], role: \"user\" }],\n\t\t\t\t\tmodel: modelName,\n\t\t\t\t});\n\n\t\t\t\tconst text = result.text;\n\n\t\t\t\tif (!text) {\n\t\t\t\t\tthrow new ApiError(\n\t\t\t\t\t\t\"Gemini returned empty content\",\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\t\"Gemini API\",\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\treturn text;\n\t\t\t} catch (error) {\n\t\t\t\tconst nextModel = models[i + 1];\n\t\t\t\tif (this.isRateLimitError(error) && nextModel) {\n\t\t\t\t\tthis.logger.warn(\n\t\t\t\t\t\t`Rate limited on ${modelName}, falling back to ${nextModel}`,\n\t\t\t\t\t\t{ model: modelName, fallback: nextModel },\n\t\t\t\t\t);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst message =\n\t\t\t\t\terror instanceof Error ? error.message : \"Unknown error\";\n\t\t\t\tthrow new ApiError(\n\t\t\t\t\t`Gemini generation failed (${modelName}): ${message}`,\n\t\t\t\t\tundefined,\n\t\t\t\t\t\"Gemini API\",\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tthis.logger.warn(\"All Gemini models exhausted\", {\n\t\t\tmodelsAttempted: models.join(\", \"),\n\t\t});\n\t\tthrow new ApiError(\"All Gemini models exhausted\", undefined, \"Gemini API\");\n\t}\n}\n"],"mappings":"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"}
@@ -13,7 +13,7 @@ declare const GitIngestResponse: Schema.Struct<{
13
13
  summary: typeof Schema.String;
14
14
  tree: typeof Schema.String;
15
15
  }>;
16
- declare function fetchRepositoryContent(repoUrl: string, maxFileSize?: string): Promise<Schema.Schema.Type<typeof GitIngestResponse>>;
16
+ declare function fetchRepositoryContent(repoUrl: string, token?: string, maxFileSize?: string): Promise<Schema.Schema.Type<typeof GitIngestResponse>>;
17
17
  //#endregion
18
18
  export { GitIngestResponse, fetchRepositoryContent };
19
19
  //# sourceMappingURL=gitingest.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"gitingest.d.ts","names":[],"sources":["../../src/utils/gitingest.ts"],"sourcesContent":[],"mappings":";;;;AAwCG,cAfU,iBAeV,EAf2B,MAAA,CAAA,MAe3B,CAAA;EAAO,OAAA,EAAA,oBAAA;;;;;;;;;;iBAHY,sBAAA,yCAGnB,QAAQ,MAAA,CAAO,MAAA,CAAO,YAAY"}
1
+ {"version":3,"file":"gitingest.d.ts","names":[],"sources":["../../src/utils/gitingest.ts"],"sourcesContent":[],"mappings":";;;;AAyCG,cAhBU,iBAgBV,EAhB2B,MAAA,CAAA,MAgB3B,CAAA;EAAO,OAAA,EAAA,oBAAA;;;;;;;;;;iBAJY,sBAAA,yDAInB,QAAQ,MAAA,CAAO,MAAA,CAAO,YAAY"}
@@ -31,13 +31,13 @@ const GitIngestResponse = Schema.Struct({
31
31
  summary: Schema.String,
32
32
  tree: Schema.String
33
33
  });
34
- async function fetchRepositoryContent(repoUrl, maxFileSize = "1118") {
34
+ async function fetchRepositoryContent(repoUrl, token, maxFileSize = "1118") {
35
35
  return Effect.runPromise(Effect.provide(post(GITINGEST_API, {
36
36
  input_text: repoUrl,
37
37
  max_file_size: maxFileSize,
38
38
  pattern: "",
39
39
  pattern_type: "exclude",
40
- token: ""
40
+ token: token ?? ""
41
41
  }, { schema: GitIngestResponse }), FetchHttpClient.layer));
42
42
  }
43
43
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"gitingest.js","names":[],"sources":["../../src/utils/gitingest.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 * as FetchHttpClient from \"@effect/platform/FetchHttpClient\";\nimport { Effect, Schema } from \"effect\";\n\nimport { post } from \"./fetcher.js\";\n\nconst GITINGEST_API = \"https://gitingest.com/api/ingest\" as const;\n\nexport const GitIngestResponse = Schema.Struct({\n\tcontent: Schema.String,\n\tdefault_max_file_size: Schema.Number,\n\tdigest_url: Schema.String,\n\tpattern: Schema.String,\n\tpattern_type: Schema.String,\n\trepo_url: Schema.String,\n\tshort_repo_url: Schema.String,\n\tsummary: Schema.String,\n\ttree: Schema.String,\n});\n\nexport async function fetchRepositoryContent(\n\trepoUrl: string,\n\tmaxFileSize: string = \"1118\",\n): Promise<Schema.Schema.Type<typeof GitIngestResponse>> {\n\treturn Effect.runPromise(\n\t\tEffect.provide(\n\t\t\tpost(\n\t\t\t\tGITINGEST_API,\n\t\t\t\t{\n\t\t\t\t\tinput_text: repoUrl,\n\t\t\t\t\tmax_file_size: maxFileSize,\n\t\t\t\t\tpattern: \"\",\n\t\t\t\t\tpattern_type: \"exclude\",\n\t\t\t\t\ttoken: \"\",\n\t\t\t\t},\n\t\t\t\t{ schema: GitIngestResponse },\n\t\t\t),\n\t\t\tFetchHttpClient.layer,\n\t\t),\n\t);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAuBA,MAAM,gBAAgB;AAEtB,MAAa,oBAAoB,OAAO,OAAO;CAC9C,SAAS,OAAO;CAChB,uBAAuB,OAAO;CAC9B,YAAY,OAAO;CACnB,SAAS,OAAO;CAChB,cAAc,OAAO;CACrB,UAAU,OAAO;CACjB,gBAAgB,OAAO;CACvB,SAAS,OAAO;CAChB,MAAM,OAAO;CACb,CAAC;AAEF,eAAsB,uBACrB,SACA,cAAsB,QACkC;AACxD,QAAO,OAAO,WACb,OAAO,QACN,KACC,eACA;EACC,YAAY;EACZ,eAAe;EACf,SAAS;EACT,cAAc;EACd,OAAO;EACP,EACD,EAAE,QAAQ,mBAAmB,CAC7B,EACD,gBAAgB,MAChB,CACD"}
1
+ {"version":3,"file":"gitingest.js","names":[],"sources":["../../src/utils/gitingest.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 * as FetchHttpClient from \"@effect/platform/FetchHttpClient\";\nimport { Effect, Schema } from \"effect\";\n\nimport { post } from \"./fetcher.js\";\n\nconst GITINGEST_API = \"https://gitingest.com/api/ingest\" as const;\n\nexport const GitIngestResponse = Schema.Struct({\n\tcontent: Schema.String,\n\tdefault_max_file_size: Schema.Number,\n\tdigest_url: Schema.String,\n\tpattern: Schema.String,\n\tpattern_type: Schema.String,\n\trepo_url: Schema.String,\n\tshort_repo_url: Schema.String,\n\tsummary: Schema.String,\n\ttree: Schema.String,\n});\n\nexport async function fetchRepositoryContent(\n\trepoUrl: string,\n\ttoken?: string,\n\tmaxFileSize: string = \"1118\",\n): Promise<Schema.Schema.Type<typeof GitIngestResponse>> {\n\treturn Effect.runPromise(\n\t\tEffect.provide(\n\t\t\tpost(\n\t\t\t\tGITINGEST_API,\n\t\t\t\t{\n\t\t\t\t\tinput_text: repoUrl,\n\t\t\t\t\tmax_file_size: maxFileSize,\n\t\t\t\t\tpattern: \"\",\n\t\t\t\t\tpattern_type: \"exclude\",\n\t\t\t\t\ttoken: token ?? \"\",\n\t\t\t\t},\n\t\t\t\t{ schema: GitIngestResponse },\n\t\t\t),\n\t\t\tFetchHttpClient.layer,\n\t\t),\n\t);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAuBA,MAAM,gBAAgB;AAEtB,MAAa,oBAAoB,OAAO,OAAO;CAC9C,SAAS,OAAO;CAChB,uBAAuB,OAAO;CAC9B,YAAY,OAAO;CACnB,SAAS,OAAO;CAChB,cAAc,OAAO;CACrB,UAAU,OAAO;CACjB,gBAAgB,OAAO;CACvB,SAAS,OAAO;CAChB,MAAM,OAAO;CACb,CAAC;AAEF,eAAsB,uBACrB,SACA,OACA,cAAsB,QACkC;AACxD,QAAO,OAAO,WACb,OAAO,QACN,KACC,eACA;EACC,YAAY;EACZ,eAAe;EACf,SAAS;EACT,cAAc;EACd,OAAO,SAAS;EAChB,EACD,EAAE,QAAQ,mBAAmB,CAC7B,EACD,gBAAgB,MAChB,CACD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elysiumoss/grepo",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "AI-powered GitHub repository management CLI — generate READMEs, topics, descriptions, and more using Gemini",
5
5
  "keywords": [
6
6
  "ai",