@coldtea/pr-lens-cli 0.1.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/LICENSE +21 -0
- package/README.md +117 -0
- package/dist/args.d.ts +18 -0
- package/dist/args.d.ts.map +1 -0
- package/dist/args.js +49 -0
- package/dist/args.js.map +1 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +8 -0
- package/dist/bin.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +97 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/analyze.d.ts +4 -0
- package/dist/commands/analyze.d.ts.map +1 -0
- package/dist/commands/analyze.js +158 -0
- package/dist/commands/analyze.js.map +1 -0
- package/dist/commands/comment.d.ts +4 -0
- package/dist/commands/comment.d.ts.map +1 -0
- package/dist/commands/comment.js +63 -0
- package/dist/commands/comment.js.map +1 -0
- package/dist/commands/export.d.ts +4 -0
- package/dist/commands/export.d.ts.map +1 -0
- package/dist/commands/export.js +44 -0
- package/dist/commands/export.js.map +1 -0
- package/dist/commands/render.d.ts +4 -0
- package/dist/commands/render.d.ts.map +1 -0
- package/dist/commands/render.js +108 -0
- package/dist/commands/render.js.map +1 -0
- package/dist/commands/validate.d.ts +4 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +49 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/comment.d.ts +16 -0
- package/dist/comment.d.ts.map +1 -0
- package/dist/comment.js +148 -0
- package/dist/comment.js.map +1 -0
- package/dist/config-file.d.ts +10 -0
- package/dist/config-file.d.ts.map +1 -0
- package/dist/config-file.js +19 -0
- package/dist/config-file.js.map +1 -0
- package/dist/corrections.d.ts +16 -0
- package/dist/corrections.d.ts.map +1 -0
- package/dist/corrections.js +80 -0
- package/dist/corrections.js.map +1 -0
- package/dist/document.d.ts +23 -0
- package/dist/document.d.ts.map +1 -0
- package/dist/document.js +75 -0
- package/dist/document.js.map +1 -0
- package/dist/errors.d.ts +15 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +14 -0
- package/dist/errors.js.map +1 -0
- package/dist/extract.d.ts +54 -0
- package/dist/extract.d.ts.map +1 -0
- package/dist/extract.js +84 -0
- package/dist/extract.js.map +1 -0
- package/dist/git.d.ts +40 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +104 -0
- package/dist/git.js.map +1 -0
- package/dist/io.d.ts +6 -0
- package/dist/io.d.ts.map +1 -0
- package/dist/io.js +35 -0
- package/dist/io.js.map +1 -0
- package/dist/prompt.d.ts +23 -0
- package/dist/prompt.d.ts.map +1 -0
- package/dist/prompt.js +69 -0
- package/dist/prompt.js.map +1 -0
- package/dist/providers/gemini.d.ts +3 -0
- package/dist/providers/gemini.d.ts.map +1 -0
- package/dist/providers/gemini.js +46 -0
- package/dist/providers/gemini.js.map +1 -0
- package/dist/providers/http.d.ts +5 -0
- package/dist/providers/http.d.ts.map +1 -0
- package/dist/providers/http.js +26 -0
- package/dist/providers/http.js.map +1 -0
- package/dist/providers/index.d.ts +48 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +71 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/openai.d.ts +17 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/openai.js +42 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/snapshot.d.ts +15 -0
- package/dist/snapshot.d.ts.map +1 -0
- package/dist/snapshot.js +77 -0
- package/dist/snapshot.js.map +1 -0
- package/dist/terminal.d.ts +6 -0
- package/dist/terminal.d.ts.map +1 -0
- package/dist/terminal.js +5 -0
- package/dist/terminal.js.map +1 -0
- package/dist/version.d.ts +4 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +4 -0
- package/dist/version.js.map +1 -0
- package/package.json +57 -0
- package/src/args.ts +52 -0
- package/src/bin.ts +10 -0
- package/src/cli.ts +120 -0
- package/src/commands/analyze.ts +206 -0
- package/src/commands/comment.ts +77 -0
- package/src/commands/export.ts +61 -0
- package/src/commands/render.ts +125 -0
- package/src/commands/validate.ts +57 -0
- package/src/comment.ts +190 -0
- package/src/config-file.ts +26 -0
- package/src/corrections.ts +86 -0
- package/src/document.ts +119 -0
- package/src/errors.ts +36 -0
- package/src/extract.ts +136 -0
- package/src/git.ts +151 -0
- package/src/io.ts +38 -0
- package/src/prompt.ts +87 -0
- package/src/providers/gemini.ts +61 -0
- package/src/providers/http.ts +54 -0
- package/src/providers/index.ts +124 -0
- package/src/providers/openai.ts +68 -0
- package/src/snapshot.ts +102 -0
- package/src/terminal.ts +9 -0
- package/src/version.ts +4 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { cutOff, postJson, unreadableResponse } from "./http.js";
|
|
3
|
+
/**
|
|
4
|
+
* Reasoning parts come back in the same array as the answer and must not be
|
|
5
|
+
* concatenated into it.
|
|
6
|
+
*/
|
|
7
|
+
const Response = z.object({
|
|
8
|
+
candidates: z
|
|
9
|
+
.array(z.object({
|
|
10
|
+
content: z
|
|
11
|
+
.object({ parts: z.array(z.object({ text: z.string().optional(), thought: z.boolean().optional() })).optional() })
|
|
12
|
+
.optional(),
|
|
13
|
+
finishReason: z.string().optional(),
|
|
14
|
+
}))
|
|
15
|
+
.optional(),
|
|
16
|
+
});
|
|
17
|
+
export const geminiCompleteJson = async (provider, request) => {
|
|
18
|
+
const body = await postJson(`${provider.baseUrl}/models/${provider.model}:generateContent`, { "x-goog-api-key": provider.apiKey }, {
|
|
19
|
+
systemInstruction: { parts: [{ text: request.system }] },
|
|
20
|
+
contents: request.turns.map((turn) => ({ role: turn.role, parts: [{ text: turn.text }] })),
|
|
21
|
+
generationConfig: {
|
|
22
|
+
responseMimeType: "application/json",
|
|
23
|
+
temperature: 0,
|
|
24
|
+
maxOutputTokens: request.maxOutputTokens,
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
const parsed = Response.safeParse(body);
|
|
28
|
+
if (!parsed.success)
|
|
29
|
+
throw unreadableResponse("Gemini", "the response did not have the shape the API documents");
|
|
30
|
+
const candidate = parsed.data.candidates?.[0];
|
|
31
|
+
if (candidate === undefined)
|
|
32
|
+
throw unreadableResponse("Gemini", "the response carried no candidate — the prompt may have been blocked");
|
|
33
|
+
const text = (candidate.content?.parts ?? [])
|
|
34
|
+
.filter((part) => part.thought !== true)
|
|
35
|
+
.map((part) => part.text ?? "")
|
|
36
|
+
.join("");
|
|
37
|
+
if (text.trim() === "") {
|
|
38
|
+
if (candidate.finishReason === "MAX_TOKENS")
|
|
39
|
+
throw cutOff("Gemini");
|
|
40
|
+
throw unreadableResponse("Gemini", `finish reason: ${candidate.finishReason ?? "unknown"}`);
|
|
41
|
+
}
|
|
42
|
+
if (candidate.finishReason === "MAX_TOKENS")
|
|
43
|
+
throw cutOff("Gemini");
|
|
44
|
+
return text;
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=gemini.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gemini.js","sourceRoot":"","sources":["../../src/providers/gemini.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAGjE;;;GAGG;AACH,MAAM,QAAQ,GAAG,CAAC,CAAC,MAAM,CAAC;IACxB,UAAU,EAAE,CAAC;SACV,KAAK,CACJ,CAAC,CAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;aACP,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC;aACjH,QAAQ,EAAE;QACb,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;KACpC,CAAC,CACH;SACA,QAAQ,EAAE;CACd,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,EACrC,QAAkB,EAClB,OAAuB,EACN,EAAE;IACnB,MAAM,IAAI,GAAG,MAAM,QAAQ,CACzB,GAAG,QAAQ,CAAC,OAAO,WAAW,QAAQ,CAAC,KAAK,kBAAkB,EAC9D,EAAE,gBAAgB,EAAE,QAAQ,CAAC,MAAM,EAAE,EACrC;QACE,iBAAiB,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE;QACxD,QAAQ,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;QAC1F,gBAAgB,EAAE;YAChB,gBAAgB,EAAE,kBAAkB;YACpC,WAAW,EAAE,CAAC;YACd,eAAe,EAAE,OAAO,CAAC,eAAe;SACzC;KACF,CACF,CAAC;IAEF,MAAM,MAAM,GAAG,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IACxC,IAAI,CAAC,MAAM,CAAC,OAAO;QACjB,MAAM,kBAAkB,CAAC,QAAQ,EAAE,uDAAuD,CAAC,CAAC;IAE9F,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC;IAC9C,IAAI,SAAS,KAAK,SAAS;QACzB,MAAM,kBAAkB,CAAC,QAAQ,EAAE,sEAAsE,CAAC,CAAC;IAE7G,MAAM,IAAI,GAAG,CAAC,SAAS,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC;SAC1C,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,KAAK,IAAI,CAAC;SACvC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;SAC9B,IAAI,CAAC,EAAE,CAAC,CAAC;IAEZ,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACvB,IAAI,SAAS,CAAC,YAAY,KAAK,YAAY;YAAE,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;QACpE,MAAM,kBAAkB,CAAC,QAAQ,EAAE,kBAAkB,SAAS,CAAC,YAAY,IAAI,SAAS,EAAE,CAAC,CAAC;IAC9F,CAAC;IAED,IAAI,SAAS,CAAC,YAAY,KAAK,YAAY;QAAE,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;IAEpE,OAAO,IAAI,CAAC;AACd,CAAC,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { PrLensCliError } from "../errors.js";
|
|
2
|
+
export declare const postJson: (url: string, headers: Record<string, string>, body: unknown) => Promise<unknown>;
|
|
3
|
+
export declare const cutOff: (provider: string) => PrLensCliError;
|
|
4
|
+
export declare const unreadableResponse: (provider: string, detail: string) => PrLensCliError;
|
|
5
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/providers/http.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAO9C,eAAO,MAAM,QAAQ,QACd,MAAM,WACF,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,QACzB,OAAO,KACZ,OAAO,CAAC,OAAO,CAgCjB,CAAC;AAEF,eAAO,MAAM,MAAM,aAAc,MAAM,KAAG,cAKvC,CAAC;AAEJ,eAAO,MAAM,kBAAkB,aAAc,MAAM,UAAU,MAAM,KAAG,cACgB,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { PrLensCliError } from "../errors.js";
|
|
2
|
+
/** Long enough for a reasoning model on a large diff, short enough to fail a stuck CI job. */
|
|
3
|
+
const REQUEST_TIMEOUT_MS = 300_000;
|
|
4
|
+
const MAX_ERROR_BODY_CHARS = 600;
|
|
5
|
+
export const postJson = async (url, headers, body) => {
|
|
6
|
+
const response = await fetch(url, {
|
|
7
|
+
method: "POST",
|
|
8
|
+
headers: { "content-type": "application/json", ...headers },
|
|
9
|
+
body: JSON.stringify(body),
|
|
10
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
11
|
+
}).catch((error) => {
|
|
12
|
+
throw new PrLensCliError("PROVIDER_FAILED", `the request to ${url} did not complete`, error instanceof Error ? error.message : String(error));
|
|
13
|
+
});
|
|
14
|
+
const text = await response.text();
|
|
15
|
+
if (!response.ok)
|
|
16
|
+
throw new PrLensCliError("PROVIDER_FAILED", `${url} answered ${response.status} ${response.statusText}`, text.slice(0, MAX_ERROR_BODY_CHARS));
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(text);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
throw new PrLensCliError("PROVIDER_FAILED", `${url} answered with something that is not JSON`, text.slice(0, MAX_ERROR_BODY_CHARS));
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
export const cutOff = (provider) => new PrLensCliError("PROVIDER_FAILED", `${provider} stopped before finishing the document`, "the answer hit the output token limit — raise --max-output-tokens, or narrow the diff with --base");
|
|
25
|
+
export const unreadableResponse = (provider, detail) => new PrLensCliError("PROVIDER_FAILED", `${provider} returned no usable text`, detail);
|
|
26
|
+
//# sourceMappingURL=http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/providers/http.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C,8FAA8F;AAC9F,MAAM,kBAAkB,GAAG,OAAO,CAAC;AAEnC,MAAM,oBAAoB,GAAG,GAAG,CAAC;AAEjC,MAAM,CAAC,MAAM,QAAQ,GAAG,KAAK,EAC3B,GAAW,EACX,OAA+B,EAC/B,IAAa,EACK,EAAE;IACpB,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QAChC,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,GAAG,OAAO,EAAE;QAC3D,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;QAC1B,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,kBAAkB,CAAC;KAChD,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;QAC1B,MAAM,IAAI,cAAc,CACtB,iBAAiB,EACjB,kBAAkB,GAAG,mBAAmB,EACxC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CACvD,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IAEnC,IAAI,CAAC,QAAQ,CAAC,EAAE;QACd,MAAM,IAAI,cAAc,CACtB,iBAAiB,EACjB,GAAG,GAAG,aAAa,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,EAC3D,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,oBAAoB,CAAC,CACpC,CAAC;IAEJ,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,cAAc,CACtB,iBAAiB,EACjB,GAAG,GAAG,2CAA2C,EACjD,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,oBAAoB,CAAC,CACpC,CAAC;IACJ,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,QAAgB,EAAkB,EAAE,CACzD,IAAI,cAAc,CAChB,iBAAiB,EACjB,GAAG,QAAQ,wCAAwC,EACnD,mGAAmG,CACpG,CAAC;AAEJ,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,QAAgB,EAAE,MAAc,EAAkB,EAAE,CACrF,IAAI,cAAc,CAAC,iBAAiB,EAAE,GAAG,QAAQ,0BAA0B,EAAE,MAAM,CAAC,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Three shapes rather than a list of vendors: Gemini's own API, OpenAI's own
|
|
3
|
+
* API, and the `/chat/completions` shape everyone else copied from it.
|
|
4
|
+
*
|
|
5
|
+
* OpenAI is separate from the servers that copied it because the two have
|
|
6
|
+
* drifted: it renamed the output limit to `max_completion_tokens` and its
|
|
7
|
+
* newer models reject `max_tokens`, which is the only spelling DeepSeek,
|
|
8
|
+
* Ollama and llama.cpp know. Everything else about the request is shared, and
|
|
9
|
+
* a new vendor still needs no code here — only a `--base-url`.
|
|
10
|
+
*/
|
|
11
|
+
export declare const PROVIDER_IDS: readonly ["gemini", "openai", "openai-compatible"];
|
|
12
|
+
export type ProviderId = (typeof PROVIDER_IDS)[number];
|
|
13
|
+
export declare const isProviderId: (value: string) => value is ProviderId;
|
|
14
|
+
export type Provider = {
|
|
15
|
+
id: ProviderId;
|
|
16
|
+
model: string;
|
|
17
|
+
apiKey: string;
|
|
18
|
+
baseUrl: string;
|
|
19
|
+
};
|
|
20
|
+
export type Turn = {
|
|
21
|
+
role: "user" | "model";
|
|
22
|
+
text: string;
|
|
23
|
+
};
|
|
24
|
+
export type JsonCompletion = {
|
|
25
|
+
system: string;
|
|
26
|
+
turns: readonly Turn[];
|
|
27
|
+
maxOutputTokens: number;
|
|
28
|
+
};
|
|
29
|
+
type ProviderDefaults = {
|
|
30
|
+
apiKeyEnv: string;
|
|
31
|
+
baseUrl: string | undefined;
|
|
32
|
+
model: string | undefined;
|
|
33
|
+
};
|
|
34
|
+
export declare const providerDefaults: (id: ProviderId) => ProviderDefaults;
|
|
35
|
+
export type ProviderOptions = {
|
|
36
|
+
id: ProviderId;
|
|
37
|
+
model: string | undefined;
|
|
38
|
+
baseUrl: string | undefined;
|
|
39
|
+
apiKeyEnv: string | undefined;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* The key is read from the environment and never accepted as an argument: a
|
|
43
|
+
* flag would land in shell history and in a CI log line.
|
|
44
|
+
*/
|
|
45
|
+
export declare const resolveProvider: (options: ProviderOptions, env: Record<string, string | undefined>) => Provider;
|
|
46
|
+
export declare const completeJson: (provider: Provider, request: JsonCompletion) => Promise<string>;
|
|
47
|
+
export {};
|
|
48
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/providers/index.ts"],"names":[],"mappings":"AAKA;;;;;;;;;GASG;AACH,eAAO,MAAM,YAAY,YAAI,QAAQ,EAAE,QAAQ,EAAE,mBAAmB,CAAU,CAAC;AAC/E,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvD,eAAO,MAAM,YAAY,UAAW,MAAM,KAAG,KAAK,IAAI,UACb,CAAC;AAE1C,MAAM,MAAM,QAAQ,GAAG;IACrB,EAAE,EAAE,UAAU,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,IAAI,GAAG;IAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5D,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,SAAS,IAAI,EAAE,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,KAAK,gBAAgB,GAAG;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3B,CAAC;AAEF,eAAO,MAAM,gBAAgB,OAAQ,UAAU,KAAG,gBAuBjD,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,UAAU,CAAC;IACf,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,eAAe,YACjB,eAAe,OACnB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,KACtC,QA6BF,CAAC;AAEF,eAAO,MAAM,YAAY,aAAc,QAAQ,WAAW,cAAc,KAAG,OAAO,CAAC,MAAM,CAWxF,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { assertNever } from "@coldtea/pr-lens-schema";
|
|
2
|
+
import { PrLensCliError } from "../errors.js";
|
|
3
|
+
import { geminiCompleteJson } from "./gemini.js";
|
|
4
|
+
import { openAiCompleteJson } from "./openai.js";
|
|
5
|
+
/**
|
|
6
|
+
* Three shapes rather than a list of vendors: Gemini's own API, OpenAI's own
|
|
7
|
+
* API, and the `/chat/completions` shape everyone else copied from it.
|
|
8
|
+
*
|
|
9
|
+
* OpenAI is separate from the servers that copied it because the two have
|
|
10
|
+
* drifted: it renamed the output limit to `max_completion_tokens` and its
|
|
11
|
+
* newer models reject `max_tokens`, which is the only spelling DeepSeek,
|
|
12
|
+
* Ollama and llama.cpp know. Everything else about the request is shared, and
|
|
13
|
+
* a new vendor still needs no code here — only a `--base-url`.
|
|
14
|
+
*/
|
|
15
|
+
export const PROVIDER_IDS = ["gemini", "openai", "openai-compatible"];
|
|
16
|
+
export const isProviderId = (value) => PROVIDER_IDS.some((id) => id === value);
|
|
17
|
+
export const providerDefaults = (id) => {
|
|
18
|
+
switch (id) {
|
|
19
|
+
case "gemini":
|
|
20
|
+
return {
|
|
21
|
+
apiKeyEnv: "GEMINI_API_KEY",
|
|
22
|
+
baseUrl: "https://generativelanguage.googleapis.com/v1beta",
|
|
23
|
+
model: "gemini-3.7-flash",
|
|
24
|
+
};
|
|
25
|
+
case "openai":
|
|
26
|
+
return {
|
|
27
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
28
|
+
baseUrl: "https://api.openai.com/v1",
|
|
29
|
+
model: undefined,
|
|
30
|
+
};
|
|
31
|
+
case "openai-compatible":
|
|
32
|
+
return {
|
|
33
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
34
|
+
baseUrl: undefined,
|
|
35
|
+
model: undefined,
|
|
36
|
+
};
|
|
37
|
+
default:
|
|
38
|
+
return assertNever(id, "Unhandled provider");
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* The key is read from the environment and never accepted as an argument: a
|
|
43
|
+
* flag would land in shell history and in a CI log line.
|
|
44
|
+
*/
|
|
45
|
+
export const resolveProvider = (options, env) => {
|
|
46
|
+
const defaults = providerDefaults(options.id);
|
|
47
|
+
const apiKeyEnv = options.apiKeyEnv ?? defaults.apiKeyEnv;
|
|
48
|
+
const apiKey = env[apiKeyEnv];
|
|
49
|
+
if (apiKey === undefined || apiKey === "")
|
|
50
|
+
throw new PrLensCliError("MISSING_API_KEY", `${apiKeyEnv} is not set`, `export ${apiKeyEnv} with your own key, or point --api-key-env at the variable that holds it`);
|
|
51
|
+
const model = options.model ?? defaults.model;
|
|
52
|
+
if (model === undefined)
|
|
53
|
+
throw new PrLensCliError("USAGE", `--model is required for the ${options.id} provider`, "the endpoint decides which model names exist, so there is no default worth guessing");
|
|
54
|
+
const baseUrl = options.baseUrl ?? defaults.baseUrl;
|
|
55
|
+
if (baseUrl === undefined)
|
|
56
|
+
throw new PrLensCliError("USAGE", "--base-url is required for an openai-compatible provider", "it is the endpoint that is compatible: point it at DeepSeek, OpenRouter, Ollama or your own server. For OpenAI itself, use --provider openai");
|
|
57
|
+
return { id: options.id, model, apiKey, baseUrl: baseUrl.replace(/\/+$/, "") };
|
|
58
|
+
};
|
|
59
|
+
export const completeJson = (provider, request) => {
|
|
60
|
+
switch (provider.id) {
|
|
61
|
+
case "gemini":
|
|
62
|
+
return geminiCompleteJson(provider, request);
|
|
63
|
+
case "openai":
|
|
64
|
+
return openAiCompleteJson(provider, request, "max_completion_tokens");
|
|
65
|
+
case "openai-compatible":
|
|
66
|
+
return openAiCompleteJson(provider, request, "max_tokens");
|
|
67
|
+
default:
|
|
68
|
+
return assertNever(provider.id, "Unhandled provider");
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/providers/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { JsonCompletion, Provider } from "./index.js";
|
|
2
|
+
/**
|
|
3
|
+
* What an output limit is called on this endpoint.
|
|
4
|
+
*
|
|
5
|
+
* OpenAI renamed the field to `max_completion_tokens` and its newer models
|
|
6
|
+
* reject the old one; the servers that copied the API before that rename —
|
|
7
|
+
* DeepSeek, Ollama, llama.cpp and the rest — only know `max_tokens`. There is
|
|
8
|
+
* no spelling both accept, so the caller says which endpoint it is talking to
|
|
9
|
+
* rather than the request guessing from a hostname.
|
|
10
|
+
*/
|
|
11
|
+
export type TokenLimitField = "max_tokens" | "max_completion_tokens";
|
|
12
|
+
/**
|
|
13
|
+
* The `/chat/completions` shape. The system prompt is the first message rather
|
|
14
|
+
* than a separate field.
|
|
15
|
+
*/
|
|
16
|
+
export declare const openAiCompleteJson: (provider: Provider, request: JsonCompletion, tokenLimitField: TokenLimitField) => Promise<string>;
|
|
17
|
+
//# sourceMappingURL=openai.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../src/providers/openai.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAa3D;;;;;;;;GAQG;AACH,MAAM,MAAM,eAAe,GAAG,YAAY,GAAG,uBAAuB,CAAC;AAErE;;;GAGG;AACH,eAAO,MAAM,kBAAkB,aACnB,QAAQ,WACT,cAAc,mBACN,eAAe,KAC/B,OAAO,CAAC,MAAM,CAiChB,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { cutOff, postJson, unreadableResponse } from "./http.js";
|
|
3
|
+
const Response = z.object({
|
|
4
|
+
choices: z
|
|
5
|
+
.array(z.object({
|
|
6
|
+
message: z.object({ content: z.string().nullable().optional() }).optional(),
|
|
7
|
+
finish_reason: z.string().nullable().optional(),
|
|
8
|
+
}))
|
|
9
|
+
.optional(),
|
|
10
|
+
});
|
|
11
|
+
/**
|
|
12
|
+
* The `/chat/completions` shape. The system prompt is the first message rather
|
|
13
|
+
* than a separate field.
|
|
14
|
+
*/
|
|
15
|
+
export const openAiCompleteJson = async (provider, request, tokenLimitField) => {
|
|
16
|
+
const body = await postJson(`${provider.baseUrl}/chat/completions`, { authorization: `Bearer ${provider.apiKey}` }, {
|
|
17
|
+
model: provider.model,
|
|
18
|
+
temperature: 0,
|
|
19
|
+
[tokenLimitField]: request.maxOutputTokens,
|
|
20
|
+
response_format: { type: "json_object" },
|
|
21
|
+
messages: [
|
|
22
|
+
{ role: "system", content: request.system },
|
|
23
|
+
...request.turns.map((turn) => ({
|
|
24
|
+
role: turn.role === "model" ? "assistant" : "user",
|
|
25
|
+
content: turn.text,
|
|
26
|
+
})),
|
|
27
|
+
],
|
|
28
|
+
});
|
|
29
|
+
const parsed = Response.safeParse(body);
|
|
30
|
+
if (!parsed.success)
|
|
31
|
+
throw unreadableResponse("the provider", "the response did not have the shape /chat/completions documents");
|
|
32
|
+
const choice = parsed.data.choices?.[0];
|
|
33
|
+
if (choice === undefined)
|
|
34
|
+
throw unreadableResponse("the provider", "the response carried no choice");
|
|
35
|
+
if (choice.finish_reason === "length")
|
|
36
|
+
throw cutOff("the provider");
|
|
37
|
+
const text = choice.message?.content ?? "";
|
|
38
|
+
if (text.trim() === "")
|
|
39
|
+
throw unreadableResponse("the provider", `finish reason: ${choice.finish_reason ?? "unknown"}`);
|
|
40
|
+
return text;
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=openai.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai.js","sourceRoot":"","sources":["../../src/providers/openai.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAGjE,MAAM,QAAQ,GAAG,CAAC,CAAC,MAAM,CAAC;IACxB,OAAO,EAAE,CAAC;SACP,KAAK,CACJ,CAAC,CAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,EAAE;QAC3E,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;KAChD,CAAC,CACH;SACA,QAAQ,EAAE;CACd,CAAC,CAAC;AAaH;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,EACrC,QAAkB,EAClB,OAAuB,EACvB,eAAgC,EACf,EAAE;IACnB,MAAM,IAAI,GAAG,MAAM,QAAQ,CACzB,GAAG,QAAQ,CAAC,OAAO,mBAAmB,EACtC,EAAE,aAAa,EAAE,UAAU,QAAQ,CAAC,MAAM,EAAE,EAAE,EAC9C;QACE,KAAK,EAAE,QAAQ,CAAC,KAAK;QACrB,WAAW,EAAE,CAAC;QACd,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC,eAAe;QAC1C,eAAe,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE;QACxC,QAAQ,EAAE;YACR,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,MAAM,EAAE;YAC3C,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBAC9B,IAAI,EAAE,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM;gBAClD,OAAO,EAAE,IAAI,CAAC,IAAI;aACnB,CAAC,CAAC;SACJ;KACF,CACF,CAAC;IAEF,MAAM,MAAM,GAAG,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IACxC,IAAI,CAAC,MAAM,CAAC,OAAO;QACjB,MAAM,kBAAkB,CAAC,cAAc,EAAE,iEAAiE,CAAC,CAAC;IAE9G,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;IACxC,IAAI,MAAM,KAAK,SAAS;QAAE,MAAM,kBAAkB,CAAC,cAAc,EAAE,gCAAgC,CAAC,CAAC;IAErG,IAAI,MAAM,CAAC,aAAa,KAAK,QAAQ;QAAE,MAAM,MAAM,CAAC,cAAc,CAAC,CAAC;IAEpE,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,EAAE,OAAO,IAAI,EAAE,CAAC;IAC3C,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE;QACpB,MAAM,kBAAkB,CAAC,cAAc,EAAE,kBAAkB,MAAM,CAAC,aAAa,IAAI,SAAS,EAAE,CAAC,CAAC;IAElG,OAAO,IAAI,CAAC;AACd,CAAC,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type GraphDoc, type Parsed } from "@coldtea/pr-lens-schema";
|
|
2
|
+
export type StoredMapOptions = {
|
|
3
|
+
id: string;
|
|
4
|
+
sha: string;
|
|
5
|
+
generatedAt: string;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* The map a repository commits: the merged state of the system, identified,
|
|
9
|
+
* stamped with the one commit it reflects, and carrying no annotations.
|
|
10
|
+
*
|
|
11
|
+
* Change statistics are dropped rather than carried over. A line count
|
|
12
|
+
* describes a diff, and this document no longer describes one.
|
|
13
|
+
*/
|
|
14
|
+
export declare const toStoredMap: (graph: GraphDoc, options: StoredMapOptions) => Parsed<GraphDoc>;
|
|
15
|
+
//# sourceMappingURL=snapshot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../src/snapshot.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,QAAQ,EACb,KAAK,MAAM,EAEZ,MAAM,yBAAyB,CAAC;AAEjC,MAAM,MAAM,gBAAgB,GAAG;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAqCF;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,UAAW,QAAQ,WAAW,gBAAgB,KAAG,MAAM,CAAC,QAAQ,CAyCvF,CAAC"}
|
package/dist/snapshot.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { applyPatch, formatIssues, graphSnapshotIssues, PrLensSchemaError, safeParseGraphDoc, } from "@coldtea/pr-lens-schema";
|
|
2
|
+
const withoutRemovedMessages = (flow) => flow.messages.filter((message) => message.delta !== "removed");
|
|
3
|
+
/**
|
|
4
|
+
* A pull-request document says what a change did; a stored map says what the
|
|
5
|
+
* system is once it has been done. So elements the change deletes go, and the
|
|
6
|
+
* rest stops being annotated.
|
|
7
|
+
*
|
|
8
|
+
* The deletions run through `applyPatch` rather than a filter, because
|
|
9
|
+
* removing a node also strands its edges, its flow steps, the drill-down views
|
|
10
|
+
* that pointed at it and the layout hints that named it — the contract already
|
|
11
|
+
* owns that cascade and there is no second version of it here.
|
|
12
|
+
*/
|
|
13
|
+
const deletionOps = (graph) => {
|
|
14
|
+
const flowOps = graph.flows.flatMap((flow) => {
|
|
15
|
+
const messages = withoutRemovedMessages(flow);
|
|
16
|
+
if (flow.delta === "removed" || messages.length === 0)
|
|
17
|
+
return [{ op: "remove_flow", id: flow.id }];
|
|
18
|
+
if (messages.length === flow.messages.length)
|
|
19
|
+
return [];
|
|
20
|
+
return [{ op: "update_flow", id: flow.id, patch: { messages } }];
|
|
21
|
+
});
|
|
22
|
+
return [
|
|
23
|
+
...flowOps,
|
|
24
|
+
...graph.edges
|
|
25
|
+
.filter((edge) => edge.delta === "removed")
|
|
26
|
+
.map((edge) => ({ op: "remove_edge", id: edge.id })),
|
|
27
|
+
...graph.nodes
|
|
28
|
+
.filter((node) => node.delta === "removed")
|
|
29
|
+
.map((node) => ({ op: "remove_node", id: node.id })),
|
|
30
|
+
...graph.lanes
|
|
31
|
+
.filter((lane) => lane.delta === "removed")
|
|
32
|
+
.map((lane) => ({ op: "remove_lane", id: lane.id })),
|
|
33
|
+
];
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* The map a repository commits: the merged state of the system, identified,
|
|
37
|
+
* stamped with the one commit it reflects, and carrying no annotations.
|
|
38
|
+
*
|
|
39
|
+
* Change statistics are dropped rather than carried over. A line count
|
|
40
|
+
* describes a diff, and this document no longer describes one.
|
|
41
|
+
*/
|
|
42
|
+
export const toStoredMap = (graph, options) => {
|
|
43
|
+
const applied = applyPatch(graph, deletionOps(graph));
|
|
44
|
+
if (!applied.ok)
|
|
45
|
+
return applied;
|
|
46
|
+
const merged = applied.value;
|
|
47
|
+
const head = { ...merged.provenance.head, sha: options.sha };
|
|
48
|
+
const candidate = {
|
|
49
|
+
...merged,
|
|
50
|
+
id: options.id,
|
|
51
|
+
generatedAt: options.generatedAt,
|
|
52
|
+
provenance: { ...merged.provenance, base: head, head },
|
|
53
|
+
stats: undefined,
|
|
54
|
+
lanes: merged.lanes.map(({ delta: _delta, ...lane }) => lane),
|
|
55
|
+
nodes: merged.nodes.map((node) => ({ ...node, delta: "unchanged" })),
|
|
56
|
+
edges: merged.edges.map((edge) => ({ ...edge, delta: "unchanged" })),
|
|
57
|
+
flows: merged.flows.map((flow) => ({
|
|
58
|
+
...flow,
|
|
59
|
+
delta: "unchanged",
|
|
60
|
+
messages: withoutRemovedMessages(flow).map((message) => ({
|
|
61
|
+
...message,
|
|
62
|
+
delta: "unchanged",
|
|
63
|
+
})),
|
|
64
|
+
})),
|
|
65
|
+
};
|
|
66
|
+
const parsed = safeParseGraphDoc(candidate);
|
|
67
|
+
if (!parsed.ok)
|
|
68
|
+
return parsed;
|
|
69
|
+
const issues = graphSnapshotIssues(parsed.value);
|
|
70
|
+
if (issues.length > 0)
|
|
71
|
+
return {
|
|
72
|
+
ok: false,
|
|
73
|
+
error: new PrLensSchemaError("NOT_A_SNAPSHOT", `the exported document is not a stored map:\n${formatIssues(issues)}`, issues),
|
|
74
|
+
};
|
|
75
|
+
return parsed;
|
|
76
|
+
};
|
|
77
|
+
//# sourceMappingURL=snapshot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../src/snapshot.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminal.d.ts","sourceRoot":"","sources":["../src/terminal.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG;IACrB,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,QAG7B,CAAC"}
|
package/dist/terminal.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminal.js","sourceRoot":"","sources":["../src/terminal.ts"],"names":[],"mappings":"AAKA,MAAM,CAAC,MAAM,eAAe,GAAa;IACvC,GAAG,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC;IAChD,GAAG,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC;CACjD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,eAAO,MAAM,WAAW,UAAU,CAAC;AAEnC,eAAO,MAAM,cAAc,gBAAgB,CAAC"}
|
package/dist/version.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,MAAM,CAAC,MAAM,WAAW,GAAG,OAAO,CAAC;AAEnC,MAAM,CAAC,MAAM,cAAc,GAAG,aAAa,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@coldtea/pr-lens-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "The PR Lens command line: turn a pull request diff into a schema-valid graph with your own model key, render it, and compose the comment.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Ohans Emmanuel",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/coldteadotai/pr-lens.git",
|
|
10
|
+
"directory": "packages/cli"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/coldteadotai/pr-lens/tree/main/packages/cli#readme",
|
|
13
|
+
"keywords": [
|
|
14
|
+
"pr-lens",
|
|
15
|
+
"code-review",
|
|
16
|
+
"architecture-diagram",
|
|
17
|
+
"data-flow",
|
|
18
|
+
"cli",
|
|
19
|
+
"byok"
|
|
20
|
+
],
|
|
21
|
+
"type": "module",
|
|
22
|
+
"bin": {
|
|
23
|
+
"pr-lens": "./dist/bin.js"
|
|
24
|
+
},
|
|
25
|
+
"exports": {
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"src",
|
|
31
|
+
"README.md",
|
|
32
|
+
"LICENSE"
|
|
33
|
+
],
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=20.11"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"yaml": "^2.8.1",
|
|
42
|
+
"zod": "^4.4.3",
|
|
43
|
+
"@coldtea/pr-lens-renderer": "^0.1.0",
|
|
44
|
+
"@coldtea/pr-lens-schema": "^0.1.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "^20.19.0",
|
|
48
|
+
"typescript": "7.0.2",
|
|
49
|
+
"vitest": "4.1.11"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
53
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
54
|
+
"test": "vitest run",
|
|
55
|
+
"test:watch": "vitest"
|
|
56
|
+
}
|
|
57
|
+
}
|
package/src/args.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { parseArgs, type ParseArgsConfig } from "node:util";
|
|
2
|
+
import { usageError } from "./errors.js";
|
|
3
|
+
|
|
4
|
+
type Options = NonNullable<ParseArgsConfig["options"]>;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* `parseArgs` throws a plain `TypeError` on an unknown flag or a missing
|
|
8
|
+
* value, which would surface as a stack trace. Every one of them is a usage
|
|
9
|
+
* mistake and reads as one.
|
|
10
|
+
*/
|
|
11
|
+
export const parseOptions = <Config extends Options>(
|
|
12
|
+
args: readonly string[],
|
|
13
|
+
options: Config,
|
|
14
|
+
): { values: Record<string, string | boolean | string[] | undefined>; positionals: string[] } => {
|
|
15
|
+
try {
|
|
16
|
+
const parsed = parseArgs({ args: [...args], options, allowPositionals: true, strict: true });
|
|
17
|
+
return { values: parsed.values, positionals: parsed.positionals };
|
|
18
|
+
} catch (error) {
|
|
19
|
+
throw usageError(error instanceof Error ? error.message : String(error));
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const readString = (value: unknown, flag: string): string | undefined => {
|
|
24
|
+
if (value === undefined) return undefined;
|
|
25
|
+
if (typeof value !== "string") throw usageError(`--${flag} needs a value`);
|
|
26
|
+
return value;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const readBoolean = (value: unknown): boolean => value === true;
|
|
30
|
+
|
|
31
|
+
export const readInt = (value: unknown, flag: string, fallback: number): number => {
|
|
32
|
+
const text = readString(value, flag);
|
|
33
|
+
if (text === undefined) return fallback;
|
|
34
|
+
|
|
35
|
+
const parsed = Number(text);
|
|
36
|
+
if (!Number.isInteger(parsed) || parsed <= 0)
|
|
37
|
+
throw usageError(`--${flag} needs a positive whole number, got ${JSON.stringify(text)}`);
|
|
38
|
+
return parsed;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const readList = (value: unknown, flag: string): string[] | undefined => {
|
|
42
|
+
if (value === undefined) return undefined;
|
|
43
|
+
if (!Array.isArray(value)) throw usageError(`--${flag} needs a value`);
|
|
44
|
+
return value.flatMap((entry) => (typeof entry === "string" ? entry.split(",") : []));
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const expectOne = (positionals: readonly string[], what: string): string => {
|
|
48
|
+
const [first, ...rest] = positionals;
|
|
49
|
+
if (first === undefined) throw usageError(`expected ${what}`);
|
|
50
|
+
if (rest.length > 0) throw usageError(`expected ${what}, got ${positionals.length} arguments`);
|
|
51
|
+
return first;
|
|
52
|
+
};
|
package/src/bin.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { run } from "./cli.js";
|
|
3
|
+
import { processTerminal } from "./terminal.js";
|
|
4
|
+
|
|
5
|
+
process.exitCode = await run(process.argv.slice(2), processTerminal, process.env).catch(
|
|
6
|
+
(error: unknown) => {
|
|
7
|
+
processTerminal.err(error instanceof Error ? (error.stack ?? error.message) : String(error));
|
|
8
|
+
return 1;
|
|
9
|
+
},
|
|
10
|
+
);
|