@coldtea/pr-lens-cli 0.8.1 → 0.10.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/dist/canvas/api.d.ts +104 -1
- package/dist/canvas/api.d.ts.map +1 -1
- package/dist/canvas/api.js +48 -1
- package/dist/canvas/api.js.map +1 -1
- package/dist/canvas/live.d.ts +15 -0
- package/dist/canvas/live.d.ts.map +1 -0
- package/dist/canvas/live.js +417 -0
- package/dist/canvas/live.js.map +1 -0
- package/dist/canvas/registry.d.ts +8 -0
- package/dist/canvas/registry.d.ts.map +1 -1
- package/dist/canvas/registry.js +2 -0
- package/dist/canvas/registry.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +25 -6
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/auth.d.ts +2 -0
- package/dist/commands/auth.d.ts.map +1 -1
- package/dist/commands/auth.js +1 -1
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/canvas.d.ts.map +1 -1
- package/dist/commands/canvas.js +29 -2
- package/dist/commands/canvas.js.map +1 -1
- package/dist/commands/comment.d.ts +1 -1
- package/dist/commands/comment.d.ts.map +1 -1
- package/dist/commands/comment.js +16 -2
- package/dist/commands/comment.js.map +1 -1
- package/dist/commands/render.d.ts +1 -1
- package/dist/commands/render.d.ts.map +1 -1
- package/dist/commands/render.js +9 -4
- package/dist/commands/render.js.map +1 -1
- package/dist/comment.d.ts +4 -0
- package/dist/comment.d.ts.map +1 -1
- package/dist/comment.js +193 -33
- package/dist/comment.js.map +1 -1
- package/dist/config-file.d.ts +7 -2
- package/dist/config-file.d.ts.map +1 -1
- package/dist/config-file.js +14 -2
- package/dist/config-file.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/git.d.ts +17 -0
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +37 -2
- package/dist/git.js.map +1 -1
- package/dist/skill-content.generated.d.ts +1 -1
- package/dist/skill-content.generated.d.ts.map +1 -1
- package/dist/skill-content.generated.js +1 -1
- package/dist/skill-content.generated.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +3 -3
- package/src/canvas/api.ts +109 -0
- package/src/canvas/live.ts +523 -0
- package/src/canvas/registry.ts +2 -0
- package/src/cli.ts +1 -0
- package/src/commands/analyze.ts +30 -6
- package/src/commands/auth.ts +1 -1
- package/src/commands/canvas.ts +36 -2
- package/src/commands/comment.ts +18 -2
- package/src/commands/render.ts +18 -5
- package/src/comment.ts +249 -34
- package/src/config-file.ts +14 -2
- package/src/errors.ts +3 -0
- package/src/git.ts +38 -3
- package/src/skill-content.generated.ts +1 -1
- package/src/version.ts +1 -1
package/src/commands/analyze.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { LENSES, type GraphDocInput, type Lens } from "@coldtea/pr-lens-schema";
|
|
1
|
+
import { LENSES, PROVIDERS, type GraphDocInput, type Lens, type Provider } from "@coldtea/pr-lens-schema";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
3
|
import { parseOptions, readBoolean, readInt, readList, readString } from "../args.js";
|
|
4
4
|
import { discoverConfig, loadConfig, type LoadedConfig } from "../config-file.js";
|
|
5
5
|
import { PrLensCliError, usageError } from "../errors.js";
|
|
6
6
|
import { extractGraph } from "../extract.js";
|
|
7
|
-
import { collectDiff, mergeBase, parseRepoSlug, remoteSlug, repositoryRoot, resolveCommit } from "../git.js";
|
|
7
|
+
import { collectDiff, mergeBase, parseRepoSlug, pullRequestUrl, remoteSlug, repositoryRoot, resolveCommit } from "../git.js";
|
|
8
8
|
import { writeJsonFile } from "../io.js";
|
|
9
9
|
import { buildExtractionPrompt, SYSTEM_PROMPT } from "../prompt.js";
|
|
10
10
|
import { completeJson, isProviderId, PROVIDER_IDS, resolveProvider } from "../providers/index.js";
|
|
@@ -36,6 +36,8 @@ environment, and the diff goes straight to the provider you name.
|
|
|
36
36
|
--pr <number> pull request number, recorded in provenance
|
|
37
37
|
--repo-slug <owner/name> override the slug read from the git remote
|
|
38
38
|
--remote <name> remote to read the slug from (default origin)
|
|
39
|
+
--forge <name> github | gitlab | bitbucket — where the repository is
|
|
40
|
+
hosted (default read from the remote host)
|
|
39
41
|
--max-diff-bytes <n> truncate the diff sent to the model (default ${DEFAULT_MAX_DIFF_BYTES})
|
|
40
42
|
--max-output-tokens <n> room for the answer (default ${DEFAULT_MAX_OUTPUT_TOKENS})
|
|
41
43
|
--dry-run report what would be sent, and send nothing
|
|
@@ -52,6 +54,16 @@ const readLenses = (values: Record<string, unknown>): Lens[] | undefined => {
|
|
|
52
54
|
return LENSES.filter((lens) => requested.includes(lens));
|
|
53
55
|
};
|
|
54
56
|
|
|
57
|
+
const readForge = (values: Record<string, unknown>): Provider | undefined => {
|
|
58
|
+
const forge = readString(values.forge, "forge");
|
|
59
|
+
if (forge === undefined) return undefined;
|
|
60
|
+
|
|
61
|
+
const known = PROVIDERS.find((provider) => provider === forge);
|
|
62
|
+
if (known === undefined)
|
|
63
|
+
throw usageError(`unknown forge ${JSON.stringify(forge)}`, `known forges: ${PROVIDERS.join(", ")}`);
|
|
64
|
+
return known;
|
|
65
|
+
};
|
|
66
|
+
|
|
55
67
|
const readProviderId = (values: Record<string, unknown>) => {
|
|
56
68
|
const id = readString(values.provider, "provider") ?? "gemini";
|
|
57
69
|
if (!isProviderId(id))
|
|
@@ -78,6 +90,7 @@ export const analyzeCommand = async (
|
|
|
78
90
|
pr: { type: "string" },
|
|
79
91
|
"repo-slug": { type: "string" },
|
|
80
92
|
remote: { type: "string" },
|
|
93
|
+
forge: { type: "string" },
|
|
81
94
|
"max-diff-bytes": { type: "string" },
|
|
82
95
|
"max-output-tokens": { type: "string" },
|
|
83
96
|
"dry-run": { type: "boolean" },
|
|
@@ -94,17 +107,28 @@ export const analyzeCommand = async (
|
|
|
94
107
|
const maxDiffBytes = readInt(values["max-diff-bytes"], "max-diff-bytes", DEFAULT_MAX_DIFF_BYTES);
|
|
95
108
|
|
|
96
109
|
const slugOption = readString(values["repo-slug"], "repo-slug");
|
|
97
|
-
const
|
|
110
|
+
const detected = slugOption === undefined
|
|
98
111
|
? await remoteSlug(repo, readString(values.remote, "remote") ?? "origin")
|
|
99
112
|
: parseRepoSlug(slugOption);
|
|
100
113
|
|
|
101
|
-
if (
|
|
114
|
+
if (detected === undefined)
|
|
102
115
|
throw new PrLensCliError(
|
|
103
116
|
"REPOSITORY_UNKNOWN",
|
|
104
117
|
"cannot tell which repository this is",
|
|
105
118
|
"pass --repo-slug owner/name, or add a git remote the CLI can read",
|
|
106
119
|
);
|
|
107
120
|
|
|
121
|
+
const forge = readForge(values);
|
|
122
|
+
// An owner/name slug carries no host, and parseRepoSlug fills in
|
|
123
|
+
// github.com — which a non-GitHub forge would then dress in the wrong
|
|
124
|
+
// URLs. Refusing beats a permalink that 404s in the reader's face.
|
|
125
|
+
if (forge !== undefined && forge !== "github" && slugOption !== undefined)
|
|
126
|
+
throw usageError(
|
|
127
|
+
`--forge ${forge} cannot be combined with --repo-slug`,
|
|
128
|
+
"an owner/name slug names no host; let the CLI read the git remote, which carries one",
|
|
129
|
+
);
|
|
130
|
+
const slug = forge === undefined ? detected : { ...detected, provider: forge };
|
|
131
|
+
|
|
108
132
|
const configPath = readString(values.config, "config");
|
|
109
133
|
const configured: LoadedConfig | undefined = readBoolean(values["no-config"])
|
|
110
134
|
? undefined
|
|
@@ -162,10 +186,10 @@ export const analyzeCommand = async (
|
|
|
162
186
|
const pullRequest =
|
|
163
187
|
pr === undefined
|
|
164
188
|
? undefined
|
|
165
|
-
: { number: pr, url:
|
|
189
|
+
: { number: pr, url: pullRequestUrl(slug, pr) };
|
|
166
190
|
|
|
167
191
|
const provenance: GraphDocInput["provenance"] = {
|
|
168
|
-
repo: slug,
|
|
192
|
+
repo: { owner: slug.owner, name: slug.name, host: slug.host },
|
|
169
193
|
base: { sha: comparedAgainst, ref: baseCommit.ref },
|
|
170
194
|
head: { sha: headCommit.sha, ref: headCommit.ref },
|
|
171
195
|
...(pullRequest === undefined ? {} : { pullRequest }),
|
package/src/commands/auth.ts
CHANGED
|
@@ -72,7 +72,7 @@ const sleep = (ms: number): Promise<void> =>
|
|
|
72
72
|
const hostOf = (api: string): string => new URL(api).host;
|
|
73
73
|
|
|
74
74
|
/** True means asked, not opened (over SSH it "succeeds" on no screen), so the link is printed either way. */
|
|
75
|
-
const askToOpen = (url: string): boolean => {
|
|
75
|
+
export const askToOpen = (url: string): boolean => {
|
|
76
76
|
// The URL came off the wire: only http(s), and never through a shell
|
|
77
77
|
// (hence `rundll32` over `cmd /c start`).
|
|
78
78
|
const scheme = ((): string => {
|
package/src/commands/canvas.ts
CHANGED
|
@@ -36,6 +36,14 @@ import { drawings, WORKSPACE_DIR } from "../workspace.js";
|
|
|
36
36
|
import { readToken, requireToken } from "../auth.js";
|
|
37
37
|
import { claimCommand } from "../canvas/claim.js";
|
|
38
38
|
import { deleteCommand } from "../canvas/delete.js";
|
|
39
|
+
import {
|
|
40
|
+
answerCommand,
|
|
41
|
+
forkCommand,
|
|
42
|
+
LIVE_USAGE,
|
|
43
|
+
lookCommand,
|
|
44
|
+
openLiveCommand,
|
|
45
|
+
showCommand,
|
|
46
|
+
} from "../canvas/live.js";
|
|
39
47
|
import { parseOptions, readBoolean, readString } from "../args.js";
|
|
40
48
|
import { PrLensCliError, usageError } from "../errors.js";
|
|
41
49
|
import { DEFAULT_API, API_ENV, readApi, requireSameApi, requireWriteToken, settleRotation, settlePendingRotation, writeCredential } from "../canvas/write.js";
|
|
@@ -53,13 +61,18 @@ const SUBCOMMANDS = [
|
|
|
53
61
|
"claim",
|
|
54
62
|
"rotate",
|
|
55
63
|
"delete",
|
|
64
|
+
"open",
|
|
65
|
+
"answer",
|
|
66
|
+
"show",
|
|
67
|
+
"fork",
|
|
68
|
+
"look",
|
|
56
69
|
] as const;
|
|
57
70
|
type Subcommand = (typeof SUBCOMMANDS)[number];
|
|
58
71
|
|
|
59
72
|
const isSubcommand = (value: string): value is Subcommand =>
|
|
60
73
|
SUBCOMMANDS.some((subcommand) => subcommand === value);
|
|
61
74
|
|
|
62
|
-
export const USAGE = `pr-lens canvas <list | push | pull | claim | rotate | delete> [options]
|
|
75
|
+
export const USAGE = `pr-lens canvas <list | push | pull | claim | rotate | delete | open | answer | show | fork | look> [options]
|
|
63
76
|
|
|
64
77
|
Keeps a graph document on the PR Lens app as a canvas: a page anyone you share
|
|
65
78
|
it with can read, and an SVG a README can embed. The write token lands in
|
|
@@ -91,6 +104,16 @@ in its fragment, so share the view link and keep the edit link to yourself.
|
|
|
91
104
|
pr-lens canvas delete permanently delete the hosted canvas; keep local graph and SVG files
|
|
92
105
|
--canvas <id|name> which canvas (default the checkout's only canvas)
|
|
93
106
|
|
|
107
|
+
Live mode puts your coding agent beside an open canvas: it answers there, with
|
|
108
|
+
the camera, the highlights and the links the page's own agent uses.
|
|
109
|
+
|
|
110
|
+
${LIVE_USAGE}
|
|
111
|
+
|
|
112
|
+
Every live command takes the drawing it is about:
|
|
113
|
+
--drawing <path> the same drawn.graph.json you opened (open takes it as
|
|
114
|
+
its argument); left out, the checkout's only paired canvas
|
|
115
|
+
--canvas <id|name> a canvas by id or name, instead of a drawing
|
|
116
|
+
|
|
94
117
|
--api <url> the PR Lens app (default $${API_ENV}, else ${DEFAULT_API})`;
|
|
95
118
|
|
|
96
119
|
type CanvasRef = {
|
|
@@ -173,6 +196,7 @@ const recordPull = (current: CanvasRegistry, pull: PullRecord): Recorded => {
|
|
|
173
196
|
...(pending === undefined ? {} : { pending }),
|
|
174
197
|
...(kept === undefined ? {} : { writeToken: kept }),
|
|
175
198
|
rev: pull.fetched?.rev ?? entry?.rev ?? 0,
|
|
199
|
+
...(entry?.live === undefined ? {} : { live: entry.live }),
|
|
176
200
|
};
|
|
177
201
|
|
|
178
202
|
return imports
|
|
@@ -639,7 +663,7 @@ export const canvasCommand = async (
|
|
|
639
663
|
const [name, ...rest] = args;
|
|
640
664
|
if (name === undefined)
|
|
641
665
|
throw usageError(
|
|
642
|
-
"canvas needs a subcommand: list, push, pull, claim, rotate or
|
|
666
|
+
"canvas needs a subcommand: list, push, pull, claim, rotate, delete, open, answer, show, fork or look",
|
|
643
667
|
);
|
|
644
668
|
if (!isSubcommand(name))
|
|
645
669
|
throw usageError(`unknown canvas subcommand ${JSON.stringify(name)}`);
|
|
@@ -657,6 +681,16 @@ export const canvasCommand = async (
|
|
|
657
681
|
return deleteCommand(rest, terminal, env);
|
|
658
682
|
case "rotate":
|
|
659
683
|
return rotate(rest, terminal, env);
|
|
684
|
+
case "open":
|
|
685
|
+
return openLiveCommand(rest, terminal, env);
|
|
686
|
+
case "answer":
|
|
687
|
+
return answerCommand(rest, terminal, env);
|
|
688
|
+
case "show":
|
|
689
|
+
return showCommand(rest, terminal, env);
|
|
690
|
+
case "fork":
|
|
691
|
+
return forkCommand(rest, terminal, env);
|
|
692
|
+
case "look":
|
|
693
|
+
return lookCommand(rest, terminal, env);
|
|
660
694
|
default:
|
|
661
695
|
return assertNever(name, "Unhandled canvas subcommand");
|
|
662
696
|
}
|
package/src/commands/comment.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { graphContentHash } from "@coldtea/pr-lens-renderer";
|
|
2
|
+
import { PROVIDERS, type Provider } from "@coldtea/pr-lens-schema";
|
|
2
3
|
import { parseOptions, readBoolean, readString } from "../args.js";
|
|
3
|
-
import {
|
|
4
|
+
import { commentMarker, composeComment } from "../comment.js";
|
|
4
5
|
import { loadConfig } from "../config-file.js";
|
|
5
6
|
import { readGraphDoc, readRenderManifest } from "../document.js";
|
|
6
7
|
import { usageError } from "../errors.js";
|
|
@@ -20,10 +21,21 @@ goes to stdout, or to a file, for whatever does the posting.
|
|
|
20
21
|
manifest records local paths
|
|
21
22
|
--config <file> read 'branding' from a repository config
|
|
22
23
|
--no-branding leave off the PR Lens footer
|
|
24
|
+
--target <forge> github | gitlab | bitbucket — who renders the
|
|
25
|
+
comment (default github). GitLab gets no <picture>
|
|
26
|
+
pair; Bitbucket gets plain markdown, no HTML
|
|
23
27
|
--print-marker print the hidden marker that identifies the
|
|
24
28
|
comment, and nothing else
|
|
25
29
|
-o, --out <file> write the markdown here instead of stdout`;
|
|
26
30
|
|
|
31
|
+
const readTarget = (values: Record<string, unknown>): Provider => {
|
|
32
|
+
const target = readString(values.target, "target") ?? "github";
|
|
33
|
+
const known = PROVIDERS.find((provider) => provider === target);
|
|
34
|
+
if (known === undefined)
|
|
35
|
+
throw usageError(`unknown target ${JSON.stringify(target)}`, `known targets: ${PROVIDERS.join(", ")}`);
|
|
36
|
+
return known;
|
|
37
|
+
};
|
|
38
|
+
|
|
27
39
|
export const commentCommand = async (args: readonly string[], terminal: Terminal): Promise<void> => {
|
|
28
40
|
const { values, positionals } = parseOptions(args, {
|
|
29
41
|
graph: { type: "string" },
|
|
@@ -31,6 +43,7 @@ export const commentCommand = async (args: readonly string[], terminal: Terminal
|
|
|
31
43
|
"asset-base-url": { type: "string" },
|
|
32
44
|
config: { type: "string" },
|
|
33
45
|
"no-branding": { type: "boolean" },
|
|
46
|
+
target: { type: "string" },
|
|
34
47
|
"print-marker": { type: "boolean" },
|
|
35
48
|
out: { type: "string", short: "o" },
|
|
36
49
|
});
|
|
@@ -38,8 +51,10 @@ export const commentCommand = async (args: readonly string[], terminal: Terminal
|
|
|
38
51
|
if (positionals.length > 0)
|
|
39
52
|
throw usageError(`comment takes no positional arguments, got ${positionals.join(" ")}`);
|
|
40
53
|
|
|
54
|
+
const target = readTarget(values);
|
|
55
|
+
|
|
41
56
|
if (readBoolean(values["print-marker"])) {
|
|
42
|
-
terminal.out(
|
|
57
|
+
terminal.out(commentMarker(target));
|
|
43
58
|
return;
|
|
44
59
|
}
|
|
45
60
|
|
|
@@ -65,6 +80,7 @@ export const commentCommand = async (args: readonly string[], terminal: Terminal
|
|
|
65
80
|
manifest,
|
|
66
81
|
assetBaseUrl: readString(values["asset-base-url"], "asset-base-url"),
|
|
67
82
|
branding: readBoolean(values["no-branding"]) ? false : (configured?.config.branding ?? true),
|
|
83
|
+
target,
|
|
68
84
|
});
|
|
69
85
|
|
|
70
86
|
const out = readString(values.out, "out");
|
package/src/commands/render.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
applyCorrections,
|
|
3
|
+
PrLensRenderError,
|
|
4
|
+
renderAll,
|
|
5
|
+
THEME_PAIR,
|
|
6
|
+
type RenderThemes,
|
|
7
|
+
} from "@coldtea/pr-lens-renderer";
|
|
2
8
|
import { safeParseGraphDoc, type Config, type GraphDoc } from "@coldtea/pr-lens-schema";
|
|
3
9
|
import { join } from "node:path";
|
|
4
10
|
import { expectOne, parseOptions, readBoolean, readString } from "../args.js";
|
|
@@ -36,20 +42,27 @@ with no sections gets one diagram per lens it declares.
|
|
|
36
42
|
|
|
37
43
|
-o, --out <dir> where the SVGs, the document and the manifest go
|
|
38
44
|
(default ${WORKSPACE_DIR}/<title>/, one directory per drawing)
|
|
39
|
-
--theme <theme> light | dark | both (default both)
|
|
45
|
+
--theme <theme> light | dark | both | neutral (default both)
|
|
46
|
+
neutral is the single self-contained render for a
|
|
47
|
+
surface that shows one image: GitLab and Bitbucket
|
|
40
48
|
--config <file> corrections to draw with (default the repository's, if any)
|
|
41
49
|
--no-config ignore the repository's corrections`;
|
|
42
50
|
|
|
43
|
-
const readThemes = (value: unknown):
|
|
51
|
+
const readThemes = (value: unknown): RenderThemes => {
|
|
44
52
|
const theme = readString(value, "theme") ?? "both";
|
|
45
53
|
switch (theme) {
|
|
46
54
|
case "light":
|
|
47
55
|
case "dark":
|
|
56
|
+
// A surface that shows one image wants this one instead of the pair, not
|
|
57
|
+
// as well as it — so it is a choice of --theme, never an addition to both.
|
|
58
|
+
case "neutral":
|
|
48
59
|
return [theme];
|
|
49
60
|
case "both":
|
|
50
|
-
return
|
|
61
|
+
return THEME_PAIR;
|
|
51
62
|
default:
|
|
52
|
-
throw usageError(
|
|
63
|
+
throw usageError(
|
|
64
|
+
`--theme takes light, dark, both or neutral, got ${JSON.stringify(theme)}`,
|
|
65
|
+
);
|
|
53
66
|
}
|
|
54
67
|
};
|
|
55
68
|
|