@elysiumoss/grepo 0.2.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.
Files changed (72) hide show
  1. package/LICENSE.md +20 -0
  2. package/README.md +61 -0
  3. package/lib/cli.d.ts +1 -0
  4. package/lib/cli.js +84 -0
  5. package/lib/cli.js.map +1 -0
  6. package/lib/commands/analyze.d.ts +11 -0
  7. package/lib/commands/analyze.d.ts.map +1 -0
  8. package/lib/commands/analyze.js +49 -0
  9. package/lib/commands/analyze.js.map +1 -0
  10. package/lib/commands/describe.d.ts +11 -0
  11. package/lib/commands/describe.d.ts.map +1 -0
  12. package/lib/commands/describe.js +89 -0
  13. package/lib/commands/describe.js.map +1 -0
  14. package/lib/commands/readme.d.ts +11 -0
  15. package/lib/commands/readme.d.ts.map +1 -0
  16. package/lib/commands/readme.js +69 -0
  17. package/lib/commands/readme.js.map +1 -0
  18. package/lib/commands/topics.d.ts +11 -0
  19. package/lib/commands/topics.d.ts.map +1 -0
  20. package/lib/commands/topics.js +74 -0
  21. package/lib/commands/topics.js.map +1 -0
  22. package/lib/config.d.ts +31 -0
  23. package/lib/config.d.ts.map +1 -0
  24. package/lib/config.js +149 -0
  25. package/lib/config.js.map +1 -0
  26. package/lib/errors.d.ts +55 -0
  27. package/lib/errors.d.ts.map +1 -0
  28. package/lib/errors.js +27 -0
  29. package/lib/errors.js.map +1 -0
  30. package/lib/index.d.ts +14 -0
  31. package/lib/index.js +14 -0
  32. package/lib/mermaid.d.ts +17 -0
  33. package/lib/mermaid.d.ts.map +1 -0
  34. package/lib/mermaid.js +132 -0
  35. package/lib/mermaid.js.map +1 -0
  36. package/lib/prompts/readme.d.ts +90 -0
  37. package/lib/prompts/readme.d.ts.map +1 -0
  38. package/lib/prompts/readme.js +345 -0
  39. package/lib/prompts/readme.js.map +1 -0
  40. package/lib/services.d.ts +29 -0
  41. package/lib/services.d.ts.map +1 -0
  42. package/lib/services.js +59 -0
  43. package/lib/services.js.map +1 -0
  44. package/lib/utils/args.d.ts +15 -0
  45. package/lib/utils/args.d.ts.map +1 -0
  46. package/lib/utils/args.js +70 -0
  47. package/lib/utils/args.js.map +1 -0
  48. package/lib/utils/fetcher.d.ts +44 -0
  49. package/lib/utils/fetcher.d.ts.map +1 -0
  50. package/lib/utils/fetcher.js +173 -0
  51. package/lib/utils/fetcher.js.map +1 -0
  52. package/lib/utils/gemini.d.ts +25 -0
  53. package/lib/utils/gemini.d.ts.map +1 -0
  54. package/lib/utils/gemini.js +108 -0
  55. package/lib/utils/gemini.js.map +1 -0
  56. package/lib/utils/github.d.ts +28 -0
  57. package/lib/utils/github.d.ts.map +1 -0
  58. package/lib/utils/github.js +94 -0
  59. package/lib/utils/github.js.map +1 -0
  60. package/lib/utils/gitingest.d.ts +19 -0
  61. package/lib/utils/gitingest.d.ts.map +1 -0
  62. package/lib/utils/gitingest.js +46 -0
  63. package/lib/utils/gitingest.js.map +1 -0
  64. package/lib/utils/logger.d.ts +47 -0
  65. package/lib/utils/logger.d.ts.map +1 -0
  66. package/lib/utils/logger.js +155 -0
  67. package/lib/utils/logger.js.map +1 -0
  68. package/lib/utils/validation.d.ts +38 -0
  69. package/lib/utils/validation.d.ts.map +1 -0
  70. package/lib/utils/validation.js +65 -0
  71. package/lib/utils/validation.js.map +1 -0
  72. package/package.json +81 -0
@@ -0,0 +1,173 @@
1
+ import { Duration, Effect, ParseResult, Schedule, Schema, pipe } from "effect";
2
+ import { HttpClient, HttpClientRequest } from "@effect/platform";
3
+ //#region src/utils/fetcher.ts
4
+ /**
5
+ *
6
+ * Copyright 2026 Mike Odnis
7
+ *
8
+ * Licensed under the Apache License, Version 2.0 (the "License");
9
+ * you may not use this file except in compliance with the License.
10
+ * You may obtain a copy of the License at
11
+ *
12
+ * http://www.apache.org/licenses/LICENSE-2.0
13
+ *
14
+ * Unless required by applicable law or agreed to in writing, software
15
+ * distributed under the License is distributed on an "AS IS" BASIS,
16
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ * See the License for the specific language governing permissions and
18
+ * limitations under the License.
19
+ *
20
+ */
21
+ const EMPTY = "";
22
+ const safeStringify = (error) => {
23
+ if (error instanceof Error) return error.message;
24
+ try {
25
+ return JSON.stringify(error) ?? String(error);
26
+ } catch {
27
+ return String(error);
28
+ }
29
+ };
30
+ var ValidationError = class ValidationError extends Error {
31
+ constructor(message, url, problems, responseData, attempt) {
32
+ super(message);
33
+ this.url = url;
34
+ this.problems = problems;
35
+ this.responseData = responseData;
36
+ this.attempt = attempt;
37
+ this.name = "ValidationError";
38
+ Object.setPrototypeOf(this, ValidationError.prototype);
39
+ }
40
+ toString() {
41
+ const attemptStr = this.attempt ? `, Attempt: ${this.attempt}` : "";
42
+ return `ValidationError: ${this.message} (URL: ${this.url}${attemptStr})`;
43
+ }
44
+ getProblemsString() {
45
+ return this.problems;
46
+ }
47
+ };
48
+ var FetcherError = class FetcherError extends Error {
49
+ constructor(message, url, status, responseData, attempt) {
50
+ super(message);
51
+ this.url = url;
52
+ this.status = status;
53
+ this.responseData = responseData;
54
+ this.attempt = attempt;
55
+ this.name = "FetcherError";
56
+ Object.setPrototypeOf(this, FetcherError.prototype);
57
+ }
58
+ toString() {
59
+ const statusStr = this.status ? `, Status: ${this.status}` : "";
60
+ const attemptStr = this.attempt ? `, Attempt: ${this.attempt}` : "";
61
+ return `FetcherError: ${this.message} (URL: ${this.url}${statusStr}${attemptStr})`;
62
+ }
63
+ };
64
+ const buildQueryString = (params) => {
65
+ if (!params) return EMPTY;
66
+ const urlParams = new URLSearchParams();
67
+ Object.entries(params).forEach(([key, value]) => {
68
+ if (value == null) return;
69
+ if (Array.isArray(value)) value.filter((item) => item != null).forEach((item) => {
70
+ urlParams.append(key, String(item));
71
+ });
72
+ else urlParams.append(key, String(value));
73
+ });
74
+ return urlParams.toString();
75
+ };
76
+ const buildRequest = (method, url) => {
77
+ switch (method) {
78
+ case "GET": return HttpClientRequest.get(url);
79
+ case "POST": return HttpClientRequest.post(url);
80
+ case "PUT": return HttpClientRequest.put(url);
81
+ case "PATCH": return HttpClientRequest.patch(url);
82
+ case "DELETE": return HttpClientRequest.del(url);
83
+ case "OPTIONS": return HttpClientRequest.options(url);
84
+ case "HEAD": return HttpClientRequest.head(url);
85
+ }
86
+ };
87
+ const validateResponse = (data, attempt, url, schema, onError) => {
88
+ if (!schema) return Effect.succeed(data);
89
+ const result = Schema.decodeUnknownEither(schema)(data);
90
+ if (result._tag === "Left") {
91
+ const validationError = new ValidationError("Response validation failed", url, ParseResult.TreeFormatter.formatIssueSync(result.left.issue), data, attempt);
92
+ if (onError) onError(validationError);
93
+ return Effect.fail(validationError);
94
+ }
95
+ return Effect.succeed(result.right);
96
+ };
97
+ const withTimeout = (eff, timeout, url, attempt) => pipe(eff, Effect.timeoutFail({
98
+ duration: Duration.millis(timeout),
99
+ onTimeout: () => new FetcherError("Request timed out", url, void 0, void 0, attempt)
100
+ }));
101
+ const createRetrySchedule = (retries, retryDelay) => pipe(Schedule.exponential(Duration.millis(retryDelay)), Schedule.intersect(Schedule.recurs(retries)), Schedule.whileInput((error) => {
102
+ if (error instanceof ValidationError) return false;
103
+ if (error instanceof FetcherError && error.status) {
104
+ if (error.status === 429) return true;
105
+ if (error.status >= 400 && error.status < 500) return false;
106
+ }
107
+ return true;
108
+ }));
109
+ const parseResponse = (response, url, attempt) => {
110
+ if (response.status < 200 || response.status >= 300) return Effect.gen(function* () {
111
+ const errorData = yield* pipe(response.json, Effect.catchAll(() => Effect.succeed(void 0)));
112
+ const errorText = yield* pipe(response.text, Effect.catchAll(() => Effect.succeed("Request failed")));
113
+ const errorMessage = response.status === 429 ? `Rate limit exceeded (429). Please slow down requests to ${url}` : `HTTP ${response.status}: ${errorText}`;
114
+ return yield* Effect.fail(new FetcherError(errorMessage, url, response.status, errorData, attempt));
115
+ });
116
+ return pipe(response.json, Effect.catchAll((error) => pipe(response.text, Effect.flatMap((text) => {
117
+ const errorMessage = `Failed to parse JSON response. Status: ${response.status}, Body: ${text.slice(0, 200)}${text.length > 200 ? "..." : ""}`;
118
+ return Effect.fail(new FetcherError(errorMessage, url, response.status, {
119
+ originalError: error,
120
+ responseText: text
121
+ }, attempt));
122
+ }), Effect.catchAll(() => Effect.fail(new FetcherError(`Failed to parse response: ${error instanceof Error ? error.message : String(error)}`, url, response.status, void 0, attempt))))));
123
+ };
124
+ function fetcher(input, method = "GET", options = {}, params, body) {
125
+ const { bodyType = "json", headers = {}, onError, retries = 0, retryDelay = 1e3, schema, timeout = 1e4 } = options;
126
+ const queryString = buildQueryString(params);
127
+ const url = queryString ? `${input}?${queryString}` : input;
128
+ return Effect.gen(function* () {
129
+ const client = yield* HttpClient.HttpClient;
130
+ let attempt = 0;
131
+ let req = buildRequest(method, url);
132
+ if (body != null && (method === "POST" || method === "PUT" || method === "PATCH")) if (bodyType === "text") {
133
+ const textBody = typeof body === "object" && body !== null ? JSON.stringify(body) : String(body ?? "");
134
+ req = HttpClientRequest.bodyText(textBody)(req);
135
+ } else req = yield* pipe(HttpClientRequest.bodyJson(body)(req), Effect.mapError((error) => new FetcherError(`Failed to serialize request body: ${safeStringify(error)}`, url, void 0, void 0, attempt)));
136
+ req = HttpClientRequest.setHeaders(headers)(req);
137
+ const retrySchedule = createRetrySchedule(retries, retryDelay);
138
+ return yield* pipe(Effect.gen(function* () {
139
+ attempt++;
140
+ return yield* validateResponse(yield* parseResponse(yield* pipe(client.execute(req), (eff) => withTimeout(eff, timeout, url, attempt), Effect.mapError((error) => {
141
+ if (error instanceof FetcherError) return error;
142
+ return new FetcherError(error instanceof Error ? error.message : String(error), url, void 0, void 0, attempt);
143
+ })), url, attempt), attempt, url, schema, onError);
144
+ }), Effect.retry(retrySchedule), Effect.catchAll((error) => {
145
+ if (error instanceof FetcherError || error instanceof ValidationError) {
146
+ if (onError) onError(error);
147
+ return Effect.fail(error);
148
+ }
149
+ const fetcherError = new FetcherError(safeStringify(error), url, void 0, void 0, attempt);
150
+ if (onError) onError(fetcherError);
151
+ return Effect.fail(fetcherError);
152
+ }));
153
+ });
154
+ }
155
+ function get(url, options, params) {
156
+ return fetcher(url, "GET", options, params);
157
+ }
158
+ function post(url, body, options, params) {
159
+ return fetcher(url, "POST", options, params, body);
160
+ }
161
+ function put(url, body, options, params) {
162
+ return fetcher(url, "PUT", options, params, body);
163
+ }
164
+ function patch(url, body, options, params) {
165
+ return fetcher(url, "PATCH", options, params, body);
166
+ }
167
+ function del(url, options, params) {
168
+ return fetcher(url, "DELETE", options, params);
169
+ }
170
+ //#endregion
171
+ export { FetcherError, ValidationError, del, fetcher, get, patch, post, put };
172
+
173
+ //# sourceMappingURL=fetcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetcher.js","names":[],"sources":["../../src/utils/fetcher.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\tHttpClient,\n\tHttpClientRequest,\n\ttype HttpClientResponse,\n} from \"@effect/platform\";\nimport { Duration, Effect, ParseResult, pipe, Schedule, Schema } from \"effect\";\n\nexport interface FetcherOptions<T = unknown> {\n\tbodyType?: \"json\" | \"text\";\n\theaders?: Record<string, string>;\n\tonError?: (error: unknown) => void;\n\tretries?: number;\n\tretryDelay?: number;\n\t// biome-ignore lint/suspicious/noExplicitAny: Effect Schema invariance requires `any` for Encoded type\n\tschema?: Schema.Schema<T, any, never>;\n\tsignal?: AbortSignal;\n\ttimeout?: number;\n}\n\nexport type HttpMethod =\n\t| \"DELETE\"\n\t| \"GET\"\n\t| \"HEAD\"\n\t| \"OPTIONS\"\n\t| \"PATCH\"\n\t| \"POST\"\n\t| \"PUT\";\nexport type QueryParams = Record<\n\tstring,\n\t| boolean\n\t| null\n\t| number\n\t| string\n\t| undefined\n\t| Array<boolean | number | string>\n>;\nexport type RequestBody =\n\t| boolean\n\t| null\n\t| number\n\t| string\n\t| unknown[]\n\t| Record<string, unknown>;\n\nconst EMPTY = \"\";\n\nconst safeStringify = (error: unknown): string => {\n\tif (error instanceof Error) {\n\t\treturn error.message;\n\t}\n\ttry {\n\t\treturn JSON.stringify(error) ?? String(error);\n\t} catch {\n\t\treturn String(error);\n\t}\n};\n\nexport class ValidationError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic readonly url: string,\n\t\tpublic readonly problems: string,\n\t\tpublic readonly responseData: unknown,\n\t\tpublic readonly attempt?: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"ValidationError\";\n\t\tObject.setPrototypeOf(this, ValidationError.prototype);\n\t}\n\n\toverride toString(): string {\n\t\tconst attemptStr = this.attempt ? `, Attempt: ${this.attempt}` : \"\";\n\t\treturn `ValidationError: ${this.message} (URL: ${this.url}${attemptStr})`;\n\t}\n\n\tgetProblemsString(): string {\n\t\treturn this.problems;\n\t}\n}\n\nexport class FetcherError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic readonly url: string,\n\t\tpublic readonly status?: number,\n\t\tpublic readonly responseData?: unknown,\n\t\tpublic readonly attempt?: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"FetcherError\";\n\t\tObject.setPrototypeOf(this, FetcherError.prototype);\n\t}\n\n\toverride toString(): string {\n\t\tconst statusStr = this.status ? `, Status: ${this.status}` : \"\";\n\t\tconst attemptStr = this.attempt ? `, Attempt: ${this.attempt}` : \"\";\n\t\treturn `FetcherError: ${this.message} (URL: ${this.url}${statusStr}${attemptStr})`;\n\t}\n}\n\nconst buildQueryString = (params?: QueryParams): string => {\n\tif (!params) {\n\t\treturn EMPTY;\n\t}\n\tconst urlParams = new URLSearchParams();\n\tObject.entries(params).forEach(([key, value]) => {\n\t\tif (value == null) {\n\t\t\treturn;\n\t\t}\n\t\tif (Array.isArray(value)) {\n\t\t\tvalue\n\t\t\t\t.filter((item): item is boolean | number | string => item != null)\n\t\t\t\t.forEach((item) => {\n\t\t\t\t\turlParams.append(key, String(item));\n\t\t\t\t});\n\t\t} else {\n\t\t\turlParams.append(key, String(value));\n\t\t}\n\t});\n\treturn urlParams.toString();\n};\n\nconst buildRequest = (\n\tmethod: HttpMethod,\n\turl: string,\n): HttpClientRequest.HttpClientRequest => {\n\tswitch (method) {\n\t\tcase \"GET\":\n\t\t\treturn HttpClientRequest.get(url);\n\t\tcase \"POST\":\n\t\t\treturn HttpClientRequest.post(url);\n\t\tcase \"PUT\":\n\t\t\treturn HttpClientRequest.put(url);\n\t\tcase \"PATCH\":\n\t\t\treturn HttpClientRequest.patch(url);\n\t\tcase \"DELETE\":\n\t\t\treturn HttpClientRequest.del(url);\n\t\tcase \"OPTIONS\":\n\t\t\treturn HttpClientRequest.options(url);\n\t\tcase \"HEAD\":\n\t\t\treturn HttpClientRequest.head(url);\n\t}\n};\n\nconst validateResponse = <T>(\n\tdata: unknown,\n\tattempt: number,\n\turl: string,\n\t// biome-ignore lint/suspicious/noExplicitAny: Effect Schema invariance requires `any` for Encoded type\n\tschema?: Schema.Schema<T, any, never>,\n\tonError?: (error: unknown) => void,\n): Effect.Effect<T, ValidationError, never> => {\n\tif (!schema) {\n\t\treturn Effect.succeed(data as T);\n\t}\n\tconst result = Schema.decodeUnknownEither(schema)(data);\n\tif (result._tag === \"Left\") {\n\t\tconst problems = ParseResult.TreeFormatter.formatIssueSync(\n\t\t\tresult.left.issue,\n\t\t);\n\t\tconst validationError = new ValidationError(\n\t\t\t\"Response validation failed\",\n\t\t\turl,\n\t\t\tproblems,\n\t\t\tdata,\n\t\t\tattempt,\n\t\t);\n\t\tif (onError) {\n\t\t\tonError(validationError);\n\t\t}\n\t\treturn Effect.fail(validationError);\n\t}\n\treturn Effect.succeed(result.right);\n};\n\nconst withTimeout = <A, E, R>(\n\teff: Effect.Effect<A, E, R>,\n\ttimeout: number,\n\turl: string,\n\tattempt: number,\n): Effect.Effect<A, E | FetcherError, R> =>\n\tpipe(\n\t\teff,\n\t\tEffect.timeoutFail({\n\t\t\tduration: Duration.millis(timeout),\n\t\t\tonTimeout: () =>\n\t\t\t\tnew FetcherError(\n\t\t\t\t\t\"Request timed out\",\n\t\t\t\t\turl,\n\t\t\t\t\tundefined,\n\t\t\t\t\tundefined,\n\t\t\t\t\tattempt,\n\t\t\t\t),\n\t\t}),\n\t);\n\nconst createRetrySchedule = (retries: number, retryDelay: number) =>\n\tpipe(\n\t\tSchedule.exponential(Duration.millis(retryDelay)),\n\t\tSchedule.intersect(Schedule.recurs(retries)),\n\t\tSchedule.whileInput((error: FetcherError | ValidationError) => {\n\t\t\tif (error instanceof ValidationError) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif (error instanceof FetcherError && error.status) {\n\t\t\t\tif (error.status === 429) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tif (error.status >= 400 && error.status < 500) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t}),\n\t);\n\nconst parseResponse = (\n\tresponse: HttpClientResponse.HttpClientResponse,\n\turl: string,\n\tattempt: number,\n): Effect.Effect<unknown, FetcherError, never> => {\n\tif (response.status < 200 || response.status >= 300) {\n\t\treturn Effect.gen(function* () {\n\t\t\tconst errorData = yield* pipe(\n\t\t\t\tresponse.json,\n\t\t\t\tEffect.catchAll(() => Effect.succeed(undefined)),\n\t\t\t);\n\t\t\tconst errorText = yield* pipe(\n\t\t\t\tresponse.text,\n\t\t\t\tEffect.catchAll(() => Effect.succeed(\"Request failed\")),\n\t\t\t);\n\t\t\tconst errorMessage =\n\t\t\t\tresponse.status === 429\n\t\t\t\t\t? `Rate limit exceeded (429). Please slow down requests to ${url}`\n\t\t\t\t\t: `HTTP ${response.status}: ${errorText}`;\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew FetcherError(\n\t\t\t\t\terrorMessage,\n\t\t\t\t\turl,\n\t\t\t\t\tresponse.status,\n\t\t\t\t\terrorData,\n\t\t\t\t\tattempt,\n\t\t\t\t),\n\t\t\t);\n\t\t});\n\t}\n\treturn pipe(\n\t\tresponse.json,\n\t\tEffect.catchAll((error) =>\n\t\t\tpipe(\n\t\t\t\tresponse.text,\n\t\t\t\tEffect.flatMap((text) => {\n\t\t\t\t\tconst errorMessage = `Failed to parse JSON response. Status: ${response.status}, Body: ${text.slice(0, 200)}${text.length > 200 ? \"...\" : \"\"}`;\n\t\t\t\t\treturn Effect.fail(\n\t\t\t\t\t\tnew FetcherError(\n\t\t\t\t\t\t\terrorMessage,\n\t\t\t\t\t\t\turl,\n\t\t\t\t\t\t\tresponse.status,\n\t\t\t\t\t\t\t{ originalError: error, responseText: text },\n\t\t\t\t\t\t\tattempt,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}),\n\t\t\t\tEffect.catchAll(() =>\n\t\t\t\t\tEffect.fail(\n\t\t\t\t\t\tnew FetcherError(\n\t\t\t\t\t\t\t`Failed to parse response: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t\t\t\t\turl,\n\t\t\t\t\t\t\tresponse.status,\n\t\t\t\t\t\t\tundefined,\n\t\t\t\t\t\t\tattempt,\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t),\n\t\t),\n\t);\n};\n\nexport function fetcher<T = unknown>(\n\tinput: string,\n\tmethod: HttpMethod = \"GET\",\n\toptions: FetcherOptions<T> = {},\n\tparams?: QueryParams,\n\tbody?: RequestBody,\n): Effect.Effect<T, FetcherError | ValidationError, HttpClient.HttpClient> {\n\tconst {\n\t\tbodyType = \"json\",\n\t\theaders = {},\n\t\tonError,\n\t\tretries = 0,\n\t\tretryDelay = 1_000,\n\t\tschema,\n\t\ttimeout = 10_000,\n\t} = options;\n\n\tconst queryString = buildQueryString(params);\n\tconst url = queryString ? `${input}?${queryString}` : input;\n\n\treturn Effect.gen(function* () {\n\t\tconst client = yield* HttpClient.HttpClient;\n\t\tlet attempt = 0;\n\n\t\tlet req = buildRequest(method, url);\n\n\t\tif (\n\t\t\tbody != null &&\n\t\t\t(method === \"POST\" || method === \"PUT\" || method === \"PATCH\")\n\t\t) {\n\t\t\tif (bodyType === \"text\") {\n\t\t\t\tconst textBody =\n\t\t\t\t\ttypeof body === \"object\" && body !== null\n\t\t\t\t\t\t? JSON.stringify(body)\n\t\t\t\t\t\t: String(body ?? \"\");\n\t\t\t\treq = HttpClientRequest.bodyText(textBody)(req);\n\t\t\t} else {\n\t\t\t\treq = yield* pipe(\n\t\t\t\t\tHttpClientRequest.bodyJson(body)(req),\n\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t(error) =>\n\t\t\t\t\t\t\tnew FetcherError(\n\t\t\t\t\t\t\t\t`Failed to serialize request body: ${safeStringify(error)}`,\n\t\t\t\t\t\t\t\turl,\n\t\t\t\t\t\t\t\tundefined,\n\t\t\t\t\t\t\t\tundefined,\n\t\t\t\t\t\t\t\tattempt,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treq = HttpClientRequest.setHeaders(headers)(req);\n\t\tconst retrySchedule = createRetrySchedule(retries, retryDelay);\n\n\t\tconst executeRequest = Effect.gen(function* () {\n\t\t\tattempt++;\n\t\t\tconst response = yield* pipe(\n\t\t\t\tclient.execute(req),\n\t\t\t\t(eff) => withTimeout(eff, timeout, url, attempt),\n\t\t\t\tEffect.mapError((error) => {\n\t\t\t\t\tif (error instanceof FetcherError) {\n\t\t\t\t\t\treturn error;\n\t\t\t\t\t}\n\t\t\t\t\treturn new FetcherError(\n\t\t\t\t\t\terror instanceof Error ? error.message : String(error),\n\t\t\t\t\t\turl,\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\tattempt,\n\t\t\t\t\t);\n\t\t\t\t}),\n\t\t\t);\n\t\t\tconst rawData = yield* parseResponse(response, url, attempt);\n\t\t\tconst validatedData = yield* validateResponse(\n\t\t\t\trawData,\n\t\t\t\tattempt,\n\t\t\t\turl,\n\t\t\t\tschema,\n\t\t\t\tonError,\n\t\t\t);\n\t\t\treturn validatedData;\n\t\t});\n\n\t\treturn yield* pipe(\n\t\t\texecuteRequest,\n\t\t\tEffect.retry(retrySchedule),\n\t\t\tEffect.catchAll((error) => {\n\t\t\t\tif (error instanceof FetcherError || error instanceof ValidationError) {\n\t\t\t\t\tif (onError) {\n\t\t\t\t\t\tonError(error);\n\t\t\t\t\t}\n\t\t\t\t\treturn Effect.fail(error);\n\t\t\t\t}\n\t\t\t\tconst fetcherError = new FetcherError(\n\t\t\t\t\tsafeStringify(error),\n\t\t\t\t\turl,\n\t\t\t\t\tundefined,\n\t\t\t\t\tundefined,\n\t\t\t\t\tattempt,\n\t\t\t\t);\n\t\t\t\tif (onError) {\n\t\t\t\t\tonError(fetcherError);\n\t\t\t\t}\n\t\t\t\treturn Effect.fail(fetcherError);\n\t\t\t}),\n\t\t);\n\t});\n}\n\nexport function get<T = unknown>(\n\turl: string,\n\toptions?: FetcherOptions<T>,\n\tparams?: QueryParams,\n) {\n\treturn fetcher<T>(url, \"GET\", options, params);\n}\n\nexport function post<T = unknown>(\n\turl: string,\n\tbody?: RequestBody,\n\toptions?: FetcherOptions<T>,\n\tparams?: QueryParams,\n) {\n\treturn fetcher<T>(url, \"POST\", options, params, body);\n}\n\nexport function put<T = unknown>(\n\turl: string,\n\tbody?: RequestBody,\n\toptions?: FetcherOptions<T>,\n\tparams?: QueryParams,\n) {\n\treturn fetcher<T>(url, \"PUT\", options, params, body);\n}\n\nexport function patch<T = unknown>(\n\turl: string,\n\tbody?: RequestBody,\n\toptions?: FetcherOptions<T>,\n\tparams?: QueryParams,\n) {\n\treturn fetcher<T>(url, \"PATCH\", options, params, body);\n}\n\nexport function del<T = unknown>(\n\turl: string,\n\toptions?: FetcherOptions<T>,\n\tparams?: QueryParams,\n) {\n\treturn fetcher<T>(url, \"DELETE\", options, params);\n}\n"],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import { Schema } from "effect";
2
+
3
+ //#region src/utils/gemini.d.ts
4
+
5
+ declare const GenerationOptions: Schema.Struct<{
6
+ model: Schema.optional<typeof Schema.String>;
7
+ temperature: Schema.optional<typeof Schema.Number>;
8
+ topK: Schema.optional<typeof Schema.Number>;
9
+ topP: Schema.optional<typeof Schema.Number>;
10
+ }>;
11
+ type GenerationOptions = Schema.Schema.Type<typeof GenerationOptions>;
12
+ declare class GeminiService {
13
+ private readonly genAI;
14
+ private readonly logger;
15
+ constructor(apiKey: string);
16
+ private cachedModels;
17
+ private fetchAvailableModels;
18
+ private parseModelVersion;
19
+ private getModelFallbackChain;
20
+ private isRateLimitError;
21
+ generateContent(prompt: string, options?: GenerationOptions): Promise<string>;
22
+ }
23
+ //#endregion
24
+ export { GeminiService, GenerationOptions };
25
+ //# sourceMappingURL=gemini.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,108 @@
1
+ import { ApiError } from "./validation.js";
2
+ import { Logger as Logger$1 } from "./logger.js";
3
+ import { Schema } from "effect";
4
+ import { GoogleGenAI } from "@google/genai";
5
+ //#region src/utils/gemini.ts
6
+ /**
7
+ *
8
+ * Copyright 2026 Mike Odnis
9
+ *
10
+ * Licensed under the Apache License, Version 2.0 (the "License");
11
+ * you may not use this file except in compliance with the License.
12
+ * You may obtain a copy of the License at
13
+ *
14
+ * http://www.apache.org/licenses/LICENSE-2.0
15
+ *
16
+ * Unless required by applicable law or agreed to in writing, software
17
+ * distributed under the License is distributed on an "AS IS" BASIS,
18
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19
+ * See the License for the specific language governing permissions and
20
+ * limitations under the License.
21
+ *
22
+ */
23
+ const GenerationOptions = Schema.Struct({
24
+ model: Schema.optional(Schema.String),
25
+ temperature: Schema.optional(Schema.Number),
26
+ topK: Schema.optional(Schema.Number),
27
+ topP: Schema.optional(Schema.Number)
28
+ });
29
+ var GeminiService = class {
30
+ genAI;
31
+ logger;
32
+ constructor(apiKey) {
33
+ this.genAI = new GoogleGenAI({ apiKey });
34
+ this.logger = new Logger$1("GeminiService");
35
+ }
36
+ cachedModels = null;
37
+ async fetchAvailableModels() {
38
+ if (this.cachedModels) return this.cachedModels;
39
+ try {
40
+ const modelsPager = await this.genAI.models.list();
41
+ const allModels = [];
42
+ for await (const model of modelsPager) allModels.push(model);
43
+ const names = allModels.filter((m) => m.name && m.supportedActions?.includes("generateContent")).map((m) => (m.name ?? "").replace("models/", ""));
44
+ this.cachedModels = names;
45
+ return names;
46
+ } catch (error) {
47
+ const errorMsg = error instanceof Error ? error.message : String(error);
48
+ this.logger.warn(`Failed to fetch models list`, { error: errorMsg });
49
+ return [];
50
+ }
51
+ }
52
+ parseModelVersion(name) {
53
+ const match = /(\d+)\.(\d+)/.exec(name);
54
+ if (!match) return 0;
55
+ return Number(match[1]) * 1e3 + Number(match[2]);
56
+ }
57
+ async getModelFallbackChain(preferred) {
58
+ const available = await this.fetchAvailableModels();
59
+ const tierOf = (name) => {
60
+ if (name.includes("flash-lite")) return 0;
61
+ if (name.includes("flash")) return 1;
62
+ if (name.includes("pro")) return 2;
63
+ return 3;
64
+ };
65
+ const ranked = available.filter((m) => !m.includes("embedding") && !m.includes("aqa") && !m.includes("imagen") && !m.includes("veo") && !m.includes("tts")).sort((a, b) => {
66
+ const tierDiff = tierOf(a) - tierOf(b);
67
+ if (tierDiff !== 0) return tierDiff;
68
+ return this.parseModelVersion(b) - this.parseModelVersion(a);
69
+ });
70
+ if (preferred) return [preferred, ...ranked.filter((m) => m !== preferred)];
71
+ return ranked.length > 0 ? ranked : ["gemini-2.0-flash", "gemini-1.5-flash"];
72
+ }
73
+ isRateLimitError(error) {
74
+ if (!(error instanceof Error)) return false;
75
+ const msg = error.message.toLowerCase();
76
+ return msg.includes("429") || msg.includes("rate limit") || msg.includes("resource exhausted") || msg.includes("quota");
77
+ }
78
+ async generateContent(prompt, options = {}) {
79
+ const models = await this.getModelFallbackChain(options.model);
80
+ for (let i = 0; i < models.length; i++) {
81
+ const modelName = models[i];
82
+ if (!modelName) continue;
83
+ try {
84
+ const text = (await this.genAI.models.generateContent({
85
+ contents: [{
86
+ parts: [{ text: prompt }],
87
+ role: "user"
88
+ }],
89
+ model: modelName
90
+ })).text;
91
+ if (!text) throw new ApiError("Gemini returned empty content", void 0, "Gemini API");
92
+ return text;
93
+ } catch (error) {
94
+ const nextModel = models[i + 1];
95
+ if (this.isRateLimitError(error) && nextModel) {
96
+ this.logger.warn(`Rate limited on ${modelName}, falling back to ${nextModel}`);
97
+ continue;
98
+ }
99
+ throw new ApiError(`Gemini generation failed (${modelName}): ${error instanceof Error ? error.message : "Unknown error"}`, void 0, "Gemini API");
100
+ }
101
+ }
102
+ throw new ApiError("All Gemini models exhausted", void 0, "Gemini API");
103
+ }
104
+ };
105
+ //#endregion
106
+ export { GeminiService, GenerationOptions };
107
+
108
+ //# sourceMappingURL=gemini.js.map
@@ -0,0 +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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"}
@@ -0,0 +1,28 @@
1
+ import { Schema } from "effect";
2
+
3
+ //#region src/utils/github.d.ts
4
+
5
+ declare const GitHubFileContent: Schema.Struct<{
6
+ content: typeof Schema.String;
7
+ encoding: typeof Schema.String;
8
+ name: typeof Schema.String;
9
+ path: typeof Schema.String;
10
+ sha: typeof Schema.String;
11
+ size: typeof Schema.Number;
12
+ }>;
13
+ declare class GitHubClient {
14
+ private readonly token?;
15
+ constructor(token?: string | undefined);
16
+ private get headers();
17
+ getFile(owner: string, repo: string, path: string): Promise<Schema.Schema.Type<typeof GitHubFileContent> | null>;
18
+ pushFile(owner: string, repo: string, path: string, content: string, message: string, branch: string): Promise<void>;
19
+ getTopics(owner: string, repo: string): Promise<readonly string[]>;
20
+ setTopics(owner: string, repo: string, topics: string[]): Promise<void>;
21
+ updateRepo(owner: string, repo: string, data: {
22
+ description?: string;
23
+ homepage?: string;
24
+ }): Promise<void>;
25
+ }
26
+ //#endregion
27
+ export { GitHubClient, GitHubFileContent };
28
+ //# sourceMappingURL=github.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.d.ts","names":[],"sources":["../../src/utils/github.ts"],"sourcesContent":[],"mappings":";;;;AA0II,cA/GS,iBA+GT,EA/G0B,MAAA,CAAA,MA+G1B,CAAA;EA0BA,OAAA,EAAA,oBAAA;EAAO,QAAA,EAAA,oBAAA;;;;;;cAhIE,YAAA;;;;sDAkBT,QAAQ,MAAA,CAAO,MAAA,CAAO,YAAY;yGAmClC;0CA6B2C;4DAoB3C;;;;MA0BA"}
@@ -0,0 +1,94 @@
1
+ import { ApiError } from "./validation.js";
2
+ import { get, patch, put } from "./fetcher.js";
3
+ import { Effect, Schema } from "effect";
4
+ import * as FetchHttpClient from "@effect/platform/FetchHttpClient";
5
+ //#region src/utils/github.ts
6
+ /**
7
+ *
8
+ * Copyright 2026 Mike Odnis
9
+ *
10
+ * Licensed under the Apache License, Version 2.0 (the "License");
11
+ * you may not use this file except in compliance with the License.
12
+ * You may obtain a copy of the License at
13
+ *
14
+ * http://www.apache.org/licenses/LICENSE-2.0
15
+ *
16
+ * Unless required by applicable law or agreed to in writing, software
17
+ * distributed under the License is distributed on an "AS IS" BASIS,
18
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19
+ * See the License for the specific language governing permissions and
20
+ * limitations under the License.
21
+ *
22
+ */
23
+ const GITHUB_API = "https://api.github.com";
24
+ const GITHUB_API_VERSION = "2022-11-28";
25
+ const GitHubFileContent = Schema.Struct({
26
+ content: Schema.String,
27
+ encoding: Schema.String,
28
+ name: Schema.String,
29
+ path: Schema.String,
30
+ sha: Schema.String,
31
+ size: Schema.Number
32
+ });
33
+ var GitHubClient = class {
34
+ constructor(token) {
35
+ this.token = token;
36
+ }
37
+ get headers() {
38
+ const headers = {
39
+ Accept: "application/vnd.github+json",
40
+ "X-GitHub-Api-Version": GITHUB_API_VERSION
41
+ };
42
+ if (this.token) headers.Authorization = `Bearer ${this.token}`;
43
+ return headers;
44
+ }
45
+ async getFile(owner, repo, path) {
46
+ try {
47
+ return Effect.runPromise(Effect.provide(get(`${GITHUB_API}/repos/${owner}/${repo}/contents/${path}`, {
48
+ headers: this.headers,
49
+ schema: GitHubFileContent
50
+ }), FetchHttpClient.layer));
51
+ } catch (error) {
52
+ if (error && typeof error === "object" && "status" in error && error.status === 404) return null;
53
+ throw new ApiError(`GitHub API error: ${error instanceof Error ? error.message : String(error)}`, void 0, "GitHub API");
54
+ }
55
+ }
56
+ async pushFile(owner, repo, path, content, message, branch) {
57
+ if (!this.token) throw new Error("GitHub token is required for push operations");
58
+ const existingFile = await this.getFile(owner, repo, path);
59
+ await Effect.runPromise(Effect.provide(put(`${GITHUB_API}/repos/${owner}/${repo}/contents/${path}`, {
60
+ branch,
61
+ content: Buffer.from(content).toString("base64"),
62
+ message,
63
+ ...existingFile && { sha: existingFile.sha }
64
+ }, { headers: {
65
+ ...this.headers,
66
+ "Content-Type": "application/json"
67
+ } }), FetchHttpClient.layer));
68
+ }
69
+ async getTopics(owner, repo) {
70
+ const TopicsResponse = Schema.Struct({ names: Schema.Array(Schema.String) });
71
+ return (await Effect.runPromise(Effect.provide(get(`${GITHUB_API}/repos/${owner}/${repo}/topics`, {
72
+ headers: this.headers,
73
+ schema: TopicsResponse
74
+ }), FetchHttpClient.layer))).names;
75
+ }
76
+ async setTopics(owner, repo, topics) {
77
+ if (!this.token) throw new Error("GitHub token is required for setting topics");
78
+ await Effect.runPromise(Effect.provide(put(`${GITHUB_API}/repos/${owner}/${repo}/topics`, { names: topics }, { headers: {
79
+ ...this.headers,
80
+ "Content-Type": "application/json"
81
+ } }), FetchHttpClient.layer));
82
+ }
83
+ async updateRepo(owner, repo, data) {
84
+ if (!this.token) throw new Error("GitHub token is required for updating repository");
85
+ await Effect.runPromise(Effect.provide(patch(`${GITHUB_API}/repos/${owner}/${repo}`, data, { headers: {
86
+ ...this.headers,
87
+ "Content-Type": "application/json"
88
+ } }), FetchHttpClient.layer));
89
+ }
90
+ };
91
+ //#endregion
92
+ export { GitHubClient, GitHubFileContent };
93
+
94
+ //# sourceMappingURL=github.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.js","names":[],"sources":["../../src/utils/github.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 { get, patch, put } from \"./fetcher.js\";\nimport { ApiError } from \"./validation.js\";\n\nconst GITHUB_API = \"https://api.github.com\" as const;\nconst GITHUB_API_VERSION = \"2022-11-28\" as const;\n\nexport const GitHubFileContent = Schema.Struct({\n\tcontent: Schema.String,\n\tencoding: Schema.String,\n\tname: Schema.String,\n\tpath: Schema.String,\n\tsha: Schema.String,\n\tsize: Schema.Number,\n});\n\nexport class GitHubClient {\n\tconstructor(private readonly token?: string) {}\n\n\tprivate get headers(): Record<string, string> {\n\t\tconst headers: Record<string, string> = {\n\t\t\tAccept: \"application/vnd.github+json\",\n\t\t\t\"X-GitHub-Api-Version\": GITHUB_API_VERSION,\n\t\t};\n\t\tif (this.token) {\n\t\t\theaders.Authorization = `Bearer ${this.token}`;\n\t\t}\n\t\treturn headers;\n\t}\n\n\tasync getFile(\n\t\towner: string,\n\t\trepo: string,\n\t\tpath: string,\n\t): Promise<Schema.Schema.Type<typeof GitHubFileContent> | null> {\n\t\ttry {\n\t\t\treturn Effect.runPromise(\n\t\t\t\tEffect.provide(\n\t\t\t\t\tget(`${GITHUB_API}/repos/${owner}/${repo}/contents/${path}`, {\n\t\t\t\t\t\theaders: this.headers,\n\t\t\t\t\t\tschema: GitHubFileContent,\n\t\t\t\t\t}),\n\t\t\t\t\tFetchHttpClient.layer,\n\t\t\t\t),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tif (\n\t\t\t\terror &&\n\t\t\t\ttypeof error === \"object\" &&\n\t\t\t\t\"status\" in error &&\n\t\t\t\terror.status === 404\n\t\t\t) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tthrow new ApiError(\n\t\t\t\t`GitHub API error: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t\tundefined,\n\t\t\t\t\"GitHub API\",\n\t\t\t);\n\t\t}\n\t}\n\n\tasync 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): Promise<void> {\n\t\tif (!this.token) {\n\t\t\tthrow new Error(\"GitHub token is required for push operations\");\n\t\t}\n\n\t\tconst existingFile = await this.getFile(owner, repo, path);\n\n\t\tawait Effect.runPromise(\n\t\t\tEffect.provide(\n\t\t\t\tput(\n\t\t\t\t\t`${GITHUB_API}/repos/${owner}/${repo}/contents/${path}`,\n\t\t\t\t\t{\n\t\t\t\t\t\tbranch,\n\t\t\t\t\t\tcontent: Buffer.from(content).toString(\"base64\"),\n\t\t\t\t\t\tmessage,\n\t\t\t\t\t\t...(existingFile && { sha: existingFile.sha }),\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t...this.headers,\n\t\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t\tFetchHttpClient.layer,\n\t\t\t),\n\t\t);\n\t}\n\n\tasync getTopics(owner: string, repo: string): Promise<readonly string[]> {\n\t\tconst TopicsResponse = Schema.Struct({\n\t\t\tnames: Schema.Array(Schema.String),\n\t\t});\n\t\tconst result = await Effect.runPromise(\n\t\t\tEffect.provide(\n\t\t\t\tget(`${GITHUB_API}/repos/${owner}/${repo}/topics`, {\n\t\t\t\t\theaders: this.headers,\n\t\t\t\t\tschema: TopicsResponse,\n\t\t\t\t}),\n\t\t\t\tFetchHttpClient.layer,\n\t\t\t),\n\t\t);\n\t\treturn result.names;\n\t}\n\n\tasync setTopics(\n\t\towner: string,\n\t\trepo: string,\n\t\ttopics: string[],\n\t): Promise<void> {\n\t\tif (!this.token) {\n\t\t\tthrow new Error(\"GitHub token is required for setting topics\");\n\t\t}\n\n\t\tawait Effect.runPromise(\n\t\t\tEffect.provide(\n\t\t\t\tput(\n\t\t\t\t\t`${GITHUB_API}/repos/${owner}/${repo}/topics`,\n\t\t\t\t\t{ names: topics },\n\t\t\t\t\t{\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t...this.headers,\n\t\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t\tFetchHttpClient.layer,\n\t\t\t),\n\t\t);\n\t}\n\n\tasync updateRepo(\n\t\towner: string,\n\t\trepo: string,\n\t\tdata: { description?: string; homepage?: string },\n\t): Promise<void> {\n\t\tif (!this.token) {\n\t\t\tthrow new Error(\"GitHub token is required for updating repository\");\n\t\t}\n\n\t\tawait Effect.runPromise(\n\t\t\tEffect.provide(\n\t\t\t\tpatch(`${GITHUB_API}/repos/${owner}/${repo}`, data, {\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t...this.headers,\n\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tFetchHttpClient.layer,\n\t\t\t),\n\t\t);\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAwBA,MAAM,aAAa;AACnB,MAAM,qBAAqB;AAE3B,MAAa,oBAAoB,OAAO,OAAO;CAC9C,SAAS,OAAO;CAChB,UAAU,OAAO;CACjB,MAAM,OAAO;CACb,MAAM,OAAO;CACb,KAAK,OAAO;CACZ,MAAM,OAAO;CACb,CAAC;AAEF,IAAa,eAAb,MAA0B;CACzB,YAAY,OAAiC;AAAhB,OAAA,QAAA;;CAE7B,IAAY,UAAkC;EAC7C,MAAM,UAAkC;GACvC,QAAQ;GACR,wBAAwB;GACxB;AACD,MAAI,KAAK,MACR,SAAQ,gBAAgB,UAAU,KAAK;AAExC,SAAO;;CAGR,MAAM,QACL,OACA,MACA,MAC+D;AAC/D,MAAI;AACH,UAAO,OAAO,WACb,OAAO,QACN,IAAI,GAAG,WAAW,SAAS,MAAM,GAAG,KAAK,YAAY,QAAQ;IAC5D,SAAS,KAAK;IACd,QAAQ;IACR,CAAC,EACF,gBAAgB,MAChB,CACD;WACO,OAAO;AACf,OACC,SACA,OAAO,UAAU,YACjB,YAAY,SACZ,MAAM,WAAW,IAEjB,QAAO;AAER,SAAM,IAAI,SACT,qBAAqB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,IAC3E,KAAA,GACA,aACA;;;CAIH,MAAM,SACL,OACA,MACA,MACA,SACA,SACA,QACgB;AAChB,MAAI,CAAC,KAAK,MACT,OAAM,IAAI,MAAM,+CAA+C;EAGhE,MAAM,eAAe,MAAM,KAAK,QAAQ,OAAO,MAAM,KAAK;AAE1D,QAAM,OAAO,WACZ,OAAO,QACN,IACC,GAAG,WAAW,SAAS,MAAM,GAAG,KAAK,YAAY,QACjD;GACC;GACA,SAAS,OAAO,KAAK,QAAQ,CAAC,SAAS,SAAS;GAChD;GACA,GAAI,gBAAgB,EAAE,KAAK,aAAa,KAAK;GAC7C,EACD,EACC,SAAS;GACR,GAAG,KAAK;GACR,gBAAgB;GAChB,EACD,CACD,EACD,gBAAgB,MAChB,CACD;;CAGF,MAAM,UAAU,OAAe,MAA0C;EACxE,MAAM,iBAAiB,OAAO,OAAO,EACpC,OAAO,OAAO,MAAM,OAAO,OAAO,EAClC,CAAC;AAUF,UATe,MAAM,OAAO,WAC3B,OAAO,QACN,IAAI,GAAG,WAAW,SAAS,MAAM,GAAG,KAAK,UAAU;GAClD,SAAS,KAAK;GACd,QAAQ;GACR,CAAC,EACF,gBAAgB,MAChB,CACD,EACa;;CAGf,MAAM,UACL,OACA,MACA,QACgB;AAChB,MAAI,CAAC,KAAK,MACT,OAAM,IAAI,MAAM,8CAA8C;AAG/D,QAAM,OAAO,WACZ,OAAO,QACN,IACC,GAAG,WAAW,SAAS,MAAM,GAAG,KAAK,UACrC,EAAE,OAAO,QAAQ,EACjB,EACC,SAAS;GACR,GAAG,KAAK;GACR,gBAAgB;GAChB,EACD,CACD,EACD,gBAAgB,MAChB,CACD;;CAGF,MAAM,WACL,OACA,MACA,MACgB;AAChB,MAAI,CAAC,KAAK,MACT,OAAM,IAAI,MAAM,mDAAmD;AAGpE,QAAM,OAAO,WACZ,OAAO,QACN,MAAM,GAAG,WAAW,SAAS,MAAM,GAAG,QAAQ,MAAM,EACnD,SAAS;GACR,GAAG,KAAK;GACR,gBAAgB;GAChB,EACD,CAAC,EACF,gBAAgB,MAChB,CACD"}
@@ -0,0 +1,19 @@
1
+ import { Schema } from "effect";
2
+
3
+ //#region src/utils/gitingest.d.ts
4
+
5
+ declare const GitIngestResponse: Schema.Struct<{
6
+ content: typeof Schema.String;
7
+ default_max_file_size: typeof Schema.Number;
8
+ digest_url: typeof Schema.String;
9
+ pattern: typeof Schema.String;
10
+ pattern_type: typeof Schema.String;
11
+ repo_url: typeof Schema.String;
12
+ short_repo_url: typeof Schema.String;
13
+ summary: typeof Schema.String;
14
+ tree: typeof Schema.String;
15
+ }>;
16
+ declare function fetchRepositoryContent(repoUrl: string, maxFileSize?: string): Promise<Schema.Schema.Type<typeof GitIngestResponse>>;
17
+ //#endregion
18
+ export { GitIngestResponse, fetchRepositoryContent };
19
+ //# sourceMappingURL=gitingest.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,46 @@
1
+ import { post } from "./fetcher.js";
2
+ import { Effect, Schema } from "effect";
3
+ import * as FetchHttpClient from "@effect/platform/FetchHttpClient";
4
+ //#region src/utils/gitingest.ts
5
+ /**
6
+ *
7
+ * Copyright 2026 Mike Odnis
8
+ *
9
+ * Licensed under the Apache License, Version 2.0 (the "License");
10
+ * you may not use this file except in compliance with the License.
11
+ * You may obtain a copy of the License at
12
+ *
13
+ * http://www.apache.org/licenses/LICENSE-2.0
14
+ *
15
+ * Unless required by applicable law or agreed to in writing, software
16
+ * distributed under the License is distributed on an "AS IS" BASIS,
17
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
+ * See the License for the specific language governing permissions and
19
+ * limitations under the License.
20
+ *
21
+ */
22
+ const GITINGEST_API = "https://gitingest.com/api/ingest";
23
+ const GitIngestResponse = Schema.Struct({
24
+ content: Schema.String,
25
+ default_max_file_size: Schema.Number,
26
+ digest_url: Schema.String,
27
+ pattern: Schema.String,
28
+ pattern_type: Schema.String,
29
+ repo_url: Schema.String,
30
+ short_repo_url: Schema.String,
31
+ summary: Schema.String,
32
+ tree: Schema.String
33
+ });
34
+ async function fetchRepositoryContent(repoUrl, maxFileSize = "1118") {
35
+ return Effect.runPromise(Effect.provide(post(GITINGEST_API, {
36
+ input_text: repoUrl,
37
+ max_file_size: maxFileSize,
38
+ pattern: "",
39
+ pattern_type: "exclude",
40
+ token: ""
41
+ }, { schema: GitIngestResponse }), FetchHttpClient.layer));
42
+ }
43
+ //#endregion
44
+ export { GitIngestResponse, fetchRepositoryContent };
45
+
46
+ //# sourceMappingURL=gitingest.js.map
@@ -0,0 +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"}
@@ -0,0 +1,47 @@
1
+ import { Schema } from "effect";
2
+
3
+ //#region src/utils/logger.d.ts
4
+
5
+ declare enum LogLevel {
6
+ NONE = 0,
7
+ ERROR = 1,
8
+ WARN = 2,
9
+ INFO = 3,
10
+ DEBUG = 4,
11
+ TRACE = 5,
12
+ ALL = 6,
13
+ }
14
+ type ColorKey = "blue" | "bold" | "cyan" | "gray" | "green" | "magenta" | "red" | "reset" | "white" | "yellow";
15
+ declare const LogDataSchema: Schema.Record$<typeof Schema.String, typeof Schema.Unknown>;
16
+ type LogData = Schema.Schema.Type<typeof LogDataSchema>;
17
+ declare const LoggerOptionsSchema: Schema.Struct<{
18
+ colorize: Schema.optional<typeof Schema.Boolean>;
19
+ filePath: Schema.optional<typeof Schema.String>;
20
+ includeTimestamp: Schema.optional<typeof Schema.Boolean>;
21
+ logToFile: Schema.optional<typeof Schema.Boolean>;
22
+ minLevel: Schema.optional<Schema.Literal<[LogLevel.NONE, LogLevel.ERROR, LogLevel.WARN, LogLevel.INFO, LogLevel.DEBUG, LogLevel.TRACE, LogLevel.ALL]>>;
23
+ }>;
24
+ type LoggerOptions = Schema.Schema.Type<typeof LoggerOptionsSchema>;
25
+ declare class Logger {
26
+ private readonly context;
27
+ private minLevel;
28
+ private static readonly instances;
29
+ constructor(context: string, options?: LoggerOptions);
30
+ static getLogger(context: string, options?: LoggerOptions): Logger;
31
+ static setGlobalLogLevel(level: LogLevel): void;
32
+ private getEffectLogLevel;
33
+ private run;
34
+ info(message: string, data?: LogData): void;
35
+ error(message: string, error?: unknown, data?: LogData): void;
36
+ warn(message: string, data?: LogData): void;
37
+ debug(message: string, data?: LogData): void;
38
+ trace(message: string, data?: LogData): void;
39
+ action(message: string, data?: LogData): void;
40
+ success(message: string, data?: LogData): void;
41
+ group(label: string): void;
42
+ groupEnd(): void;
43
+ time<T>(label: string, fn: () => Promise<T> | T): Promise<T>;
44
+ }
45
+ //#endregion
46
+ export { ColorKey, LogData, LogDataSchema, LogLevel, Logger, LoggerOptions, LoggerOptionsSchema };
47
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","names":[],"sources":["../../src/utils/logger.ts"],"sourcesContent":[],"mappings":";;;;aAyBY,QAAA;;;;;;;;;KAUA,QAAA;cAYC,eAAa,MAAA,CAAA,eAAA,MAAA,CAAA,eAAA,MAAA,CAAA;AAMM,KAFpB,OAAA,GAAU,MAAA,CAAO,MAAA,CAAO,IAEJ,CAAA,OAFgB,aAEhB,CAAA;AAAA,cAAnB,mBAAmB,EAAA,MAAA,CAAA,MAAA,CAAA;EAiBpB,QAAA,iBAAa,CAAA,qBAA6B,CAAA;EAEzC,QAAA,iBAAM,CAAA,oBAAA,CAAA;EAKoB,gBAAA,iBAAA,CAAA,qBAAA,CAAA;EAOa,SAAA,iBAAA,CAAA,qBAAA,CAAA;EAAgB,QAAA,iBAAA,eAAA,CAAA,cAAA,gBAAA,eAAA,eAAA,gBAAA,gBAAA,cAAA,CAAA,CAAA,CAAA;CAS5B,CAAA;AAyCV,KAhElB,aAAA,GAAgB,MAAA,CAAO,MAAA,CAAO,IAgEZ,CAAA,OAhEwB,mBAgExB,CAAA;AAOkB,cArEnC,MAAA,CAqEmC;EAmBlB,iBAAA,OAAA;EAOC,QAAA,QAAA;EAOA,wBAAA,SAAA;EAWC,WAAA,CAAA,OAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EA5GO,aA4GP;EAWC,OAAA,SAAA,CAAA,OAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAhHmB,aAgHnB,CAAA,EAhHmC,MAgHnC;EAyBe,OAAA,iBAAA,CAAA,KAAA,EAhIR,QAgIQ,CAAA,EAAA,IAAA;EAAR,QAAA,iBAAA;EAAa,QAAA,GAAA;EAAY,IAAA,CAAA,OAAA,EAAA,MAAA,EAAA,IAAA,CAAA,EAvFnC,OAuFmC,CAAA,EAAA,IAAA;EAAR,KAAA,CAAA,OAAA,EAAA,MAAA,EAAA,KAAA,CAAA,EAAA,OAAA,EAAA,IAAA,CAAA,EAhFT,OAgFS,CAAA,EAAA,IAAA;EAAO,IAAA,CAAA,OAAA,EAAA,MAAA,EAAA,IAAA,CAAA,EA7DlC,OA6DkC,CAAA,EAAA,IAAA;gCAtDjC;gCAOA;iCAWC;kCAWC;;;mCAyBO,QAAQ,KAAK,IAAI,QAAQ"}