@repolensai/cli 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -0
- package/bin/repolens.mjs +3518 -0
- package/lib/auth-store.d.ts +21 -0
- package/lib/auth-store.mjs +68 -0
- package/lib/git-history.d.ts +214 -0
- package/lib/git-history.mjs +1064 -0
- package/lib/history-metadata-shared.d.ts +18 -0
- package/lib/history-metadata-shared.mjs +87 -0
- package/lib/history-report-shared.d.ts +6 -0
- package/lib/history-report-shared.mjs +410 -0
- package/lib/protocol-handler.d.ts +15 -0
- package/lib/protocol-handler.mjs +158 -0
- package/lib/publish-shared.d.ts +14 -0
- package/lib/publish-shared.mjs +299 -0
- package/lib/runtime-events.d.ts +31 -0
- package/lib/runtime-events.mjs +80 -0
- package/package.json +38 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
function shellQuote(value) {
|
|
2
|
+
return `'${String(value).replaceAll("'", `'\"'\"'`)}'`;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function cmdQuote(value) {
|
|
6
|
+
return `"${String(value).replaceAll('"', '""')}"`;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {string} rawUrl
|
|
11
|
+
*/
|
|
12
|
+
export function parseRepoLensProtocolUrl(rawUrl) {
|
|
13
|
+
let parsed;
|
|
14
|
+
try {
|
|
15
|
+
parsed = new URL(rawUrl);
|
|
16
|
+
} catch {
|
|
17
|
+
throw new Error("Invalid RepoLens launch URL.");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (!["repolens-cli:", "repolens:"].includes(parsed.protocol) || parsed.hostname !== "run") {
|
|
21
|
+
throw new Error("Unsupported RepoLens launch URL.");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const command = parsed.searchParams.get("command")?.trim() ?? "";
|
|
25
|
+
if (!command) {
|
|
26
|
+
throw new Error("RepoLens launch URL is missing a command.");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
command,
|
|
31
|
+
boardId: parsed.searchParams.get("boardId")?.trim() || null,
|
|
32
|
+
source: parsed.searchParams.get("source")?.trim() || null,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {string} command
|
|
38
|
+
*/
|
|
39
|
+
export function isSafeRepoLensCommand(command) {
|
|
40
|
+
return typeof command === "string"
|
|
41
|
+
&& command.startsWith("repolens ")
|
|
42
|
+
&& !/[\u0000\r\n;&|<>`$]/.test(command);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @param {{
|
|
47
|
+
* command: string;
|
|
48
|
+
* cliEntrypoint: string;
|
|
49
|
+
* nodePath: string;
|
|
50
|
+
* repoRoot?: string | null;
|
|
51
|
+
* source?: string | null;
|
|
52
|
+
* }} input
|
|
53
|
+
*/
|
|
54
|
+
export function buildTerminalLaunchScript({ command, cliEntrypoint, nodePath, repoRoot = null, source = null }) {
|
|
55
|
+
if (!isSafeRepoLensCommand(command)) {
|
|
56
|
+
throw new Error("Refusing to launch an unsafe RepoLens command.");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const commandArgs = command.slice("repolens ".length).trim();
|
|
60
|
+
const lines = [
|
|
61
|
+
`REPOLENS_CMD=${shellQuote(command)}`,
|
|
62
|
+
`REPOLENS_NODE=${shellQuote(nodePath)}`,
|
|
63
|
+
`REPOLENS_ENTRY=${shellQuote(cliEntrypoint)}`,
|
|
64
|
+
`REPOLENS_ARGS=${shellQuote(commandArgs)}`,
|
|
65
|
+
`REPOLENS_REPO_ROOT=${shellQuote(repoRoot ?? "")}`,
|
|
66
|
+
`REPOLENS_SOURCE=${shellQuote(source ?? "")}`,
|
|
67
|
+
"",
|
|
68
|
+
"printf 'RepoLens CLI handoff\\n\\n'",
|
|
69
|
+
'if [[ -n "$REPOLENS_SOURCE" ]]; then',
|
|
70
|
+
' printf "Source: %s\\n" "$REPOLENS_SOURCE"',
|
|
71
|
+
"fi",
|
|
72
|
+
'printf "Command:\\n%s\\n\\n" "$REPOLENS_CMD"',
|
|
73
|
+
'if [[ -n "$REPOLENS_REPO_ROOT" ]]; then',
|
|
74
|
+
' printf "Working directory:\\n%s\\n\\n" "$REPOLENS_REPO_ROOT"',
|
|
75
|
+
"else",
|
|
76
|
+
" printf 'Working directory:\\nChoose it in Terminal before confirming.\\n\\n'",
|
|
77
|
+
"fi",
|
|
78
|
+
'printf "Run this command? [y/N] "',
|
|
79
|
+
"read -r REPOLENS_CONFIRM",
|
|
80
|
+
'case "$REPOLENS_CONFIRM" in',
|
|
81
|
+
" [Yy]|[Yy][Ee][Ss]) ;;",
|
|
82
|
+
" *) echo 'Cancelled.'; exit 0 ;;",
|
|
83
|
+
"esac",
|
|
84
|
+
'if [[ -n "$REPOLENS_REPO_ROOT" ]]; then',
|
|
85
|
+
' cd "$REPOLENS_REPO_ROOT" || exit 1',
|
|
86
|
+
"fi",
|
|
87
|
+
'eval "set -- $REPOLENS_ARGS"',
|
|
88
|
+
'"$REPOLENS_NODE" "$REPOLENS_ENTRY" "$@"',
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
return lines.join("\n");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @param {{
|
|
96
|
+
* command: string;
|
|
97
|
+
* cliEntrypoint: string;
|
|
98
|
+
* nodePath: string;
|
|
99
|
+
* repoRoot?: string | null;
|
|
100
|
+
* source?: string | null;
|
|
101
|
+
* }} input
|
|
102
|
+
*/
|
|
103
|
+
export function buildWindowsTerminalLaunchScript({ command, cliEntrypoint, nodePath, repoRoot = null, source = null }) {
|
|
104
|
+
if (!isSafeRepoLensCommand(command)) {
|
|
105
|
+
throw new Error("Refusing to launch an unsafe RepoLens command.");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const commandArgs = command.slice("repolens ".length).trim();
|
|
109
|
+
const lines = [
|
|
110
|
+
"@echo off",
|
|
111
|
+
`set "REPOLENS_CMD=${command}"`,
|
|
112
|
+
`set "REPOLENS_NODE=${nodePath}"`,
|
|
113
|
+
`set "REPOLENS_ENTRY=${cliEntrypoint}"`,
|
|
114
|
+
`set "REPOLENS_ARGS=${commandArgs}"`,
|
|
115
|
+
`set "REPOLENS_REPO_ROOT=${repoRoot ?? ""}"`,
|
|
116
|
+
`set "REPOLENS_SOURCE=${source ?? ""}"`,
|
|
117
|
+
"echo RepoLens CLI handoff",
|
|
118
|
+
"echo.",
|
|
119
|
+
'if not "%REPOLENS_SOURCE%"=="" echo Source: %REPOLENS_SOURCE%',
|
|
120
|
+
"echo Command:",
|
|
121
|
+
"echo %REPOLENS_CMD%",
|
|
122
|
+
"echo.",
|
|
123
|
+
'if not "%REPOLENS_REPO_ROOT%"=="" (',
|
|
124
|
+
" echo Working directory:",
|
|
125
|
+
" echo %REPOLENS_REPO_ROOT%",
|
|
126
|
+
") else (",
|
|
127
|
+
" echo Working directory:",
|
|
128
|
+
" echo Choose it in Terminal before confirming.",
|
|
129
|
+
")",
|
|
130
|
+
"echo.",
|
|
131
|
+
'choice /C YN /N /M "Run this command? [Y/N] "',
|
|
132
|
+
"if errorlevel 2 goto :eof",
|
|
133
|
+
'if not "%REPOLENS_REPO_ROOT%"=="" cd /d "%REPOLENS_REPO_ROOT%"',
|
|
134
|
+
`call ${cmdQuote(nodePath)} ${cmdQuote(cliEntrypoint)} ${commandArgs}`,
|
|
135
|
+
"",
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
return lines.join("\r\n");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* @param {{
|
|
143
|
+
* scriptPath: string;
|
|
144
|
+
* }} input
|
|
145
|
+
*/
|
|
146
|
+
export function buildLinuxDesktopEntry({ scriptPath }) {
|
|
147
|
+
return `[Desktop Entry]
|
|
148
|
+
Type=Application
|
|
149
|
+
Name=RepoLens Launcher
|
|
150
|
+
Comment=Handle RepoLens browser-to-terminal handoff
|
|
151
|
+
Exec=${scriptPath} %u
|
|
152
|
+
Terminal=false
|
|
153
|
+
NoDisplay=true
|
|
154
|
+
MimeType=x-scheme-handler/repolens-cli;
|
|
155
|
+
StartupNotify=false
|
|
156
|
+
X-Desktop-File-Install-Version=0.27
|
|
157
|
+
`;
|
|
158
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function buildEvidenceRows(boardId: string, evidenceValue: unknown, commitSha: string | null): Array<Record<string, unknown>>;
|
|
2
|
+
export function buildDiagramRows(params: {
|
|
3
|
+
analysisId: string;
|
|
4
|
+
boardId: string;
|
|
5
|
+
publishKind: string;
|
|
6
|
+
audience: string;
|
|
7
|
+
diagrams: unknown;
|
|
8
|
+
}): Array<Record<string, unknown>>;
|
|
9
|
+
export function collectExplainerRelatedFiles(result: Record<string, unknown>): string[];
|
|
10
|
+
export function collectChangeBriefRelatedFiles(result: Record<string, unknown>): string[];
|
|
11
|
+
export function exportExplainerToMarkdown(result: Record<string, unknown>): string;
|
|
12
|
+
export function exportChangeBriefToMarkdown(result: Record<string, unknown>): string;
|
|
13
|
+
export function normalizeChangeBriefAudience(value: string): string;
|
|
14
|
+
export function normalizeExplainerAudience(value: string): string;
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
export function buildEvidenceRows(boardId, evidenceValue, commitSha) {
|
|
2
|
+
return readArray(evidenceValue).map((item) => {
|
|
3
|
+
const record = asRecord(item);
|
|
4
|
+
|
|
5
|
+
return {
|
|
6
|
+
board_id: boardId,
|
|
7
|
+
evidence_type: stringOrFallback(record.type, "file"),
|
|
8
|
+
reference: stringOrFallback(record.reference, "unknown"),
|
|
9
|
+
description: stringOrFallback(record.description, ""),
|
|
10
|
+
commit_sha: typeof commitSha === "string" ? commitSha : null,
|
|
11
|
+
line_start: readLinePart(record.lineRange, 0),
|
|
12
|
+
line_end: readLinePart(record.lineRange, 1),
|
|
13
|
+
// Persist only references and metadata, not raw source snippets.
|
|
14
|
+
code_snippet: null,
|
|
15
|
+
confidence: normalizeEvidenceConfidence(record.confidence),
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function buildDiagramRows(params) {
|
|
21
|
+
return readArray(params.diagrams)
|
|
22
|
+
.map(asRecord)
|
|
23
|
+
.filter((diagram) => nullableString(diagram.mermaidSource))
|
|
24
|
+
.map((diagram) => ({
|
|
25
|
+
analysis_id: params.analysisId,
|
|
26
|
+
board_id: params.boardId,
|
|
27
|
+
type: "diagram",
|
|
28
|
+
perspective: stringOrFallback(diagram.type, params.publishKind),
|
|
29
|
+
audience: params.audience,
|
|
30
|
+
title: stringOrFallback(diagram.title, "Diagram"),
|
|
31
|
+
content: stringOrFallback(diagram.mermaidSource, ""),
|
|
32
|
+
content_format: "mermaid",
|
|
33
|
+
confidence_score: 0.72,
|
|
34
|
+
confidence_reason: "local_agent_structured_output",
|
|
35
|
+
is_inferred: true,
|
|
36
|
+
related_files: readStringArray(diagram.relatedFiles),
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function collectExplainerRelatedFiles(result) {
|
|
41
|
+
const files = new Set();
|
|
42
|
+
|
|
43
|
+
for (const component of readArray(result.keyComponents).map(asRecord)) {
|
|
44
|
+
for (const file of readStringArray(component.files)) {
|
|
45
|
+
files.add(file);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
for (const diagram of readArray(result.diagrams).map(asRecord)) {
|
|
50
|
+
for (const file of readStringArray(diagram.relatedFiles)) {
|
|
51
|
+
files.add(file);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
for (const evidence of readArray(result.evidence).map(asRecord)) {
|
|
56
|
+
const reference = nullableString(evidence.reference);
|
|
57
|
+
if (reference) {
|
|
58
|
+
files.add(reference);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return Array.from(files);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function collectChangeBriefRelatedFiles(result) {
|
|
66
|
+
const files = new Set();
|
|
67
|
+
|
|
68
|
+
for (const item of readArray(result.impactedFiles).map(asRecord)) {
|
|
69
|
+
const filePath = nullableString(item.path);
|
|
70
|
+
if (filePath) {
|
|
71
|
+
files.add(filePath);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for (const diagram of readArray(result.diagrams).map(asRecord)) {
|
|
76
|
+
for (const file of readStringArray(diagram.relatedFiles)) {
|
|
77
|
+
files.add(file);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
for (const evidence of readArray(result.evidence).map(asRecord)) {
|
|
82
|
+
const reference = nullableString(evidence.reference);
|
|
83
|
+
if (reference) {
|
|
84
|
+
files.add(reference);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return Array.from(files);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function exportExplainerToMarkdown(result) {
|
|
92
|
+
const lines = [];
|
|
93
|
+
|
|
94
|
+
lines.push(`# ${stringOrFallback(result.title, "Explainer")}`);
|
|
95
|
+
lines.push("");
|
|
96
|
+
lines.push(`> ${stringOrFallback(result.summary, "")}`);
|
|
97
|
+
lines.push("");
|
|
98
|
+
|
|
99
|
+
if (nullableString(result.valueProposition)) {
|
|
100
|
+
lines.push("## Value Proposition");
|
|
101
|
+
lines.push(String(result.valueProposition));
|
|
102
|
+
lines.push("");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (nullableString(result.userJourney)) {
|
|
106
|
+
lines.push("## User Journey");
|
|
107
|
+
lines.push(String(result.userJourney));
|
|
108
|
+
lines.push("");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const keyComponents = readArray(result.keyComponents).map(asRecord);
|
|
112
|
+
if (keyComponents.length > 0) {
|
|
113
|
+
lines.push("## Key Components");
|
|
114
|
+
for (const component of keyComponents) {
|
|
115
|
+
lines.push(`- **${stringOrFallback(component.name, "Untitled Component")}** — ${stringOrFallback(component.role, "")}`);
|
|
116
|
+
const files = readStringArray(component.files);
|
|
117
|
+
if (files.length > 0) {
|
|
118
|
+
lines.push(` - Files: ${files.map((file) => `\`${file}\``).join(", ")}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
lines.push("");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (nullableString(result.businessLogic)) {
|
|
125
|
+
lines.push("## Business Logic");
|
|
126
|
+
lines.push(String(result.businessLogic));
|
|
127
|
+
lines.push("");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
for (const diagram of readArray(result.diagrams).map(asRecord)) {
|
|
131
|
+
lines.push(`## ${stringOrFallback(diagram.title, "Diagram")}`);
|
|
132
|
+
lines.push("");
|
|
133
|
+
lines.push("```mermaid");
|
|
134
|
+
lines.push(stringOrFallback(diagram.mermaidSource, ""));
|
|
135
|
+
lines.push("```");
|
|
136
|
+
lines.push("");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (nullableString(result.risksAndQuestions)) {
|
|
140
|
+
lines.push("## Risks & Questions");
|
|
141
|
+
lines.push(String(result.risksAndQuestions));
|
|
142
|
+
lines.push("");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const openQuestions = readStringArray(result.openQuestions);
|
|
146
|
+
if (openQuestions.length > 0) {
|
|
147
|
+
lines.push("## Open Questions");
|
|
148
|
+
for (const question of openQuestions) {
|
|
149
|
+
lines.push(`- ${question}`);
|
|
150
|
+
}
|
|
151
|
+
lines.push("");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
appendEvidence(lines, result.evidence);
|
|
155
|
+
|
|
156
|
+
lines.push("---");
|
|
157
|
+
lines.push("*Generated by RepoLens CLI*");
|
|
158
|
+
|
|
159
|
+
return lines.join("\n");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function exportChangeBriefToMarkdown(result) {
|
|
163
|
+
const lines = [];
|
|
164
|
+
|
|
165
|
+
lines.push("# Change Brief");
|
|
166
|
+
lines.push("");
|
|
167
|
+
lines.push(`> ${stringOrFallback(result.summary, "")}`);
|
|
168
|
+
lines.push("");
|
|
169
|
+
|
|
170
|
+
for (const feature of readArray(result.impactedFeatures).map(asRecord)) {
|
|
171
|
+
if (lines[lines.length - 1] !== "## Impacted Features" && !lines.includes("## Impacted Features")) {
|
|
172
|
+
lines.push("## Impacted Features");
|
|
173
|
+
}
|
|
174
|
+
lines.push(`- **${stringOrFallback(feature.feature, "Feature")}** (${stringOrFallback(feature.impactType, "changed")}) — ${stringOrFallback(feature.description, "")}`);
|
|
175
|
+
}
|
|
176
|
+
if (lines[lines.length - 1] === "## Impacted Features") {
|
|
177
|
+
lines.pop();
|
|
178
|
+
} else if (lines.includes("## Impacted Features")) {
|
|
179
|
+
lines.push("");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const impactedFiles = readArray(result.impactedFiles).map(asRecord);
|
|
183
|
+
if (impactedFiles.length > 0) {
|
|
184
|
+
lines.push("## Changed Files");
|
|
185
|
+
for (const item of impactedFiles) {
|
|
186
|
+
lines.push(`- \`${stringOrFallback(item.path, "unknown")}\` (${stringOrFallback(item.changeType, "modified")}) — ${stringOrFallback(item.role, "")}`);
|
|
187
|
+
}
|
|
188
|
+
lines.push("");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const impactedScreens = readStringArray(result.impactedScreens);
|
|
192
|
+
if (impactedScreens.length > 0) {
|
|
193
|
+
lines.push("## Impacted Screens");
|
|
194
|
+
for (const screen of impactedScreens) {
|
|
195
|
+
lines.push(`- ${screen}`);
|
|
196
|
+
}
|
|
197
|
+
lines.push("");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (nullableString(result.rolloutRisks)) {
|
|
201
|
+
lines.push("## Rollout Risks");
|
|
202
|
+
lines.push(String(result.rolloutRisks));
|
|
203
|
+
lines.push("");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const qaChecklist = readStringArray(result.qaChecklist);
|
|
207
|
+
if (qaChecklist.length > 0) {
|
|
208
|
+
lines.push("## QA Checklist");
|
|
209
|
+
for (const item of qaChecklist) {
|
|
210
|
+
lines.push(`- [ ] ${item}`);
|
|
211
|
+
}
|
|
212
|
+
lines.push("");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (nullableString(result.communicationBrief)) {
|
|
216
|
+
lines.push("## Communication Brief");
|
|
217
|
+
lines.push(String(result.communicationBrief));
|
|
218
|
+
lines.push("");
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
for (const diagram of readArray(result.diagrams).map(asRecord)) {
|
|
222
|
+
lines.push(`## ${stringOrFallback(diagram.title, "Diagram")}`);
|
|
223
|
+
lines.push("");
|
|
224
|
+
lines.push("```mermaid");
|
|
225
|
+
lines.push(stringOrFallback(diagram.mermaidSource, ""));
|
|
226
|
+
lines.push("```");
|
|
227
|
+
lines.push("");
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
appendEvidence(lines, result.evidence);
|
|
231
|
+
|
|
232
|
+
lines.push("---");
|
|
233
|
+
lines.push("*Generated by RepoLens CLI*");
|
|
234
|
+
|
|
235
|
+
return lines.join("\n");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function normalizeChangeBriefAudience(value) {
|
|
239
|
+
return value === "engineer" || value === "qa" ? value : "pm";
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function normalizeExplainerAudience(value) {
|
|
243
|
+
return value === "engineer" || value === "technical_deep_dive" ? value : "pm";
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function appendEvidence(lines, evidenceValue) {
|
|
247
|
+
const evidence = readArray(evidenceValue).map(asRecord);
|
|
248
|
+
if (evidence.length === 0) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
lines.push("## Evidence");
|
|
253
|
+
for (const item of evidence) {
|
|
254
|
+
const confidence = normalizeEvidenceConfidence(item.confidence);
|
|
255
|
+
const badge = confidence === "explicit" ? "✓" : confidence === "strongly_inferred" ? "~" : "?";
|
|
256
|
+
lines.push(`- [${badge}] **${stringOrFallback(item.reference, "unknown")}** — ${stringOrFallback(item.description, "")}`);
|
|
257
|
+
}
|
|
258
|
+
lines.push("");
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function readLinePart(value, index) {
|
|
262
|
+
if (!Array.isArray(value)) {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const candidate = value[index];
|
|
267
|
+
return typeof candidate === "number" && !Number.isNaN(candidate) ? candidate : null;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function normalizeEvidenceConfidence(value) {
|
|
271
|
+
if (value === "explicit" || value === "strongly_inferred" || value === "hypothesis") {
|
|
272
|
+
return value;
|
|
273
|
+
}
|
|
274
|
+
return "hypothesis";
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function readArray(value) {
|
|
278
|
+
return Array.isArray(value) ? value : [];
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function readStringArray(value) {
|
|
282
|
+
return readArray(value).map(String).filter(Boolean);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function stringOrFallback(value, fallback) {
|
|
286
|
+
return typeof value === "string" && value.length > 0 ? value : fallback;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function nullableString(value) {
|
|
290
|
+
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function asRecord(value) {
|
|
294
|
+
return isPlainObject(value) ? value : {};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function isPlainObject(value) {
|
|
298
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
299
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface CliRuntimeAuthConfig {
|
|
2
|
+
appUrl?: string | null;
|
|
3
|
+
token?: string | null;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface CliRuntimeEvent {
|
|
7
|
+
action:
|
|
8
|
+
| "cli_login.browser_open_failed"
|
|
9
|
+
| "cli_protocol.install_failed"
|
|
10
|
+
| "cli_protocol.install_succeeded"
|
|
11
|
+
| "cli_protocol.launch_invalid_url"
|
|
12
|
+
| "cli_protocol.launch_missing_repo_link"
|
|
13
|
+
| "cli_protocol.launch_opened"
|
|
14
|
+
| "cli_protocol.launch_received"
|
|
15
|
+
| "cli_protocol.launch_terminal_failed"
|
|
16
|
+
| "cli_protocol.launch_unsafe_command"
|
|
17
|
+
| "cli_protocol.launch_unsupported_platform";
|
|
18
|
+
boardId?: string | null;
|
|
19
|
+
targetId?: string | null;
|
|
20
|
+
metadata?: Record<string, unknown> | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function normalizeCliRuntimeEvent(value: unknown): CliRuntimeEvent | null;
|
|
24
|
+
|
|
25
|
+
export function reportCliRuntimeEvent(
|
|
26
|
+
authConfig: CliRuntimeAuthConfig,
|
|
27
|
+
event: unknown,
|
|
28
|
+
options?: {
|
|
29
|
+
fetchImpl?: typeof fetch;
|
|
30
|
+
}
|
|
31
|
+
): Promise<boolean>;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const CLI_RUNTIME_ACTIONS = new Set([
|
|
2
|
+
"cli_login.browser_open_failed",
|
|
3
|
+
"cli_protocol.install_failed",
|
|
4
|
+
"cli_protocol.install_succeeded",
|
|
5
|
+
"cli_protocol.launch_invalid_url",
|
|
6
|
+
"cli_protocol.launch_missing_repo_link",
|
|
7
|
+
"cli_protocol.launch_opened",
|
|
8
|
+
"cli_protocol.launch_received",
|
|
9
|
+
"cli_protocol.launch_terminal_failed",
|
|
10
|
+
"cli_protocol.launch_unsafe_command",
|
|
11
|
+
"cli_protocol.launch_unsupported_platform",
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {unknown} value
|
|
16
|
+
*/
|
|
17
|
+
function normalizeMetadata(value) {
|
|
18
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return /** @type {Record<string, unknown>} */ (value);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {unknown} value
|
|
27
|
+
*/
|
|
28
|
+
export function normalizeCliRuntimeEvent(value) {
|
|
29
|
+
if (!value || typeof value !== "object") {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const candidate = /** @type {Record<string, unknown>} */ (value);
|
|
34
|
+
const action = typeof candidate.action === "string" ? candidate.action : null;
|
|
35
|
+
if (!action || !CLI_RUNTIME_ACTIONS.has(action)) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
action,
|
|
41
|
+
boardId: typeof candidate.boardId === "string" && candidate.boardId.trim() ? candidate.boardId.trim() : null,
|
|
42
|
+
targetId: typeof candidate.targetId === "string" && candidate.targetId.trim() ? candidate.targetId.trim() : null,
|
|
43
|
+
metadata: normalizeMetadata(candidate.metadata),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {{
|
|
49
|
+
* appUrl?: string | null;
|
|
50
|
+
* token?: string | null;
|
|
51
|
+
* }} authConfig
|
|
52
|
+
* @param {unknown} event
|
|
53
|
+
* @param {{
|
|
54
|
+
* fetchImpl?: typeof fetch;
|
|
55
|
+
* }} [options]
|
|
56
|
+
*/
|
|
57
|
+
export async function reportCliRuntimeEvent(authConfig, event, options = {}) {
|
|
58
|
+
const normalized = normalizeCliRuntimeEvent(event);
|
|
59
|
+
if (!normalized || !authConfig?.appUrl || !authConfig?.token) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
64
|
+
const url = new URL("/api/cli/events", `${authConfig.appUrl}/`);
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const response = await fetchImpl(url, {
|
|
68
|
+
method: "POST",
|
|
69
|
+
headers: {
|
|
70
|
+
authorization: `Bearer ${authConfig.token}`,
|
|
71
|
+
"content-type": "application/json",
|
|
72
|
+
},
|
|
73
|
+
body: JSON.stringify(normalized),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return response.ok;
|
|
77
|
+
} catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@repolensai/cli",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "RepoLens terminal client for local agent orchestration and board publishing",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"preferGlobal": true,
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"repolens": "bin/repolens.mjs"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"bin",
|
|
15
|
+
"lib",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"keywords": [
|
|
19
|
+
"repolens",
|
|
20
|
+
"cli",
|
|
21
|
+
"codex",
|
|
22
|
+
"claude",
|
|
23
|
+
"git"
|
|
24
|
+
],
|
|
25
|
+
"homepage": "https://github.com/Michal-Pi/repolense/tree/main/packages/cli",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/Michal-Pi/repolense.git",
|
|
29
|
+
"directory": "packages/cli"
|
|
30
|
+
},
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/Michal-Pi/repolense/issues"
|
|
33
|
+
},
|
|
34
|
+
"license": "UNLICENSED",
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=20"
|
|
37
|
+
}
|
|
38
|
+
}
|