backtrace-console 0.0.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 +22 -0
- package/app.js +22 -0
- package/bin/backtrace-cli.js +22 -0
- package/bin/www +90 -0
- package/lib/BacktraceCodexTool.js +32 -0
- package/lib/backtrace/analysis.js +356 -0
- package/lib/backtrace/constants.js +23 -0
- package/lib/backtrace/options.js +278 -0
- package/lib/backtrace/query.js +940 -0
- package/lib/backtrace/repair-fingerprint.js +405 -0
- package/lib/backtrace/repair.js +495 -0
- package/lib/backtrace/tool.js +333 -0
- package/lib/backtrace/utils.js +297 -0
- package/lib/cli/args.js +177 -0
- package/lib/cli/run.js +191 -0
- package/package.json +29 -0
- package/public/__inline_check__.js +451 -0
- package/public/index.html +642 -0
- package/public/stylesheets/style.css +186 -0
- package/routes/backtrace.js +864 -0
- package/routes/index.js +9 -0
- package/routes/users.js +9 -0
package/lib/cli/args.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// 解析 bin/backtrace-cli.js 使用的轻量命令行语法,
|
|
2
|
+
// 把 argv 数组转换成后续流程需要的标准化选项对象。
|
|
3
|
+
function printCliUsage() {
|
|
4
|
+
console.log([
|
|
5
|
+
"Usage:",
|
|
6
|
+
" backtrace <command> [options]",
|
|
7
|
+
"",
|
|
8
|
+
"Commands:",
|
|
9
|
+
" list",
|
|
10
|
+
" fingerprint",
|
|
11
|
+
" collect",
|
|
12
|
+
" summarize-errors",
|
|
13
|
+
" fix-plan",
|
|
14
|
+
" apply-fix",
|
|
15
|
+
"",
|
|
16
|
+
"Shared options:",
|
|
17
|
+
" --fingerprint <value[,value2,...]>",
|
|
18
|
+
" --from <unixTs> default: 1",
|
|
19
|
+
" --to <unixTs> default: now",
|
|
20
|
+
" --query-url <url>",
|
|
21
|
+
" --proxy <url>",
|
|
22
|
+
" --workdir <dir>",
|
|
23
|
+
" --storage-dir <dir>",
|
|
24
|
+
" --download-concurrency <n>",
|
|
25
|
+
"",
|
|
26
|
+
"Fix-plan options:",
|
|
27
|
+
" --fingerprint <value> required for backtrace fix-plan",
|
|
28
|
+
" --report-path <path> relative path under fingerprints/",
|
|
29
|
+
" --repair-plan-path <path> relative path of existing repair plan file",
|
|
30
|
+
" --repair-version <value>",
|
|
31
|
+
" --plan-file <path> load plan text from file",
|
|
32
|
+
" --plan-text <text> pass plan text directly",
|
|
33
|
+
"",
|
|
34
|
+
"Examples:",
|
|
35
|
+
" backtrace list",
|
|
36
|
+
" backtrace fingerprint",
|
|
37
|
+
" backtrace fingerprint db221f7a4f76...",
|
|
38
|
+
" backtrace collect --fingerprint db221f7",
|
|
39
|
+
" backtrace summarize-errors --fingerprint db221f7a4f76...",
|
|
40
|
+
" backtrace fix-plan --fingerprint db221f7a4f76...",
|
|
41
|
+
" backtrace apply-fix --fingerprint db221f7a4f76...",
|
|
42
|
+
].join("\n"));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 保持显式解析,这样每个支持的参数都只映射到一个确定字段,
|
|
46
|
+
// 调用方也能明确区分“显示帮助”和“执行命令”两种模式。
|
|
47
|
+
function parseCliArgs(argv) {
|
|
48
|
+
const args = Array.from(argv || []);
|
|
49
|
+
if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
|
|
50
|
+
return { help: true };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const command = args[0];
|
|
54
|
+
let index = 1;
|
|
55
|
+
|
|
56
|
+
const options = {
|
|
57
|
+
command,
|
|
58
|
+
fingerprint: "",
|
|
59
|
+
objectId: "",
|
|
60
|
+
from: "",
|
|
61
|
+
to: "",
|
|
62
|
+
queryUrl: "",
|
|
63
|
+
proxy: "",
|
|
64
|
+
workdir: "",
|
|
65
|
+
storageDir: "",
|
|
66
|
+
downloadConcurrency: "",
|
|
67
|
+
limit: "",
|
|
68
|
+
offset: "",
|
|
69
|
+
select: "",
|
|
70
|
+
reportPath: "",
|
|
71
|
+
repairPlanPath: "",
|
|
72
|
+
repairVersion: "",
|
|
73
|
+
planFile: "",
|
|
74
|
+
planText: "",
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// 按顺序解析参数,因为这里只支持简单的
|
|
78
|
+
// "--name value" 形式,以及一个可选的 fix-plan 子命令。
|
|
79
|
+
for (; index < args.length; index += 1) {
|
|
80
|
+
const arg = args[index];
|
|
81
|
+
if (!arg.startsWith("-") && command === "fingerprint" && !options.fingerprint) {
|
|
82
|
+
options.fingerprint = arg;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (arg === "--fingerprint") {
|
|
86
|
+
options.fingerprint = args[index + 1] || "";
|
|
87
|
+
index += 1;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (arg === "--object-id") {
|
|
91
|
+
options.objectId = args[index + 1] || "";
|
|
92
|
+
index += 1;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (arg === "--from") {
|
|
96
|
+
options.from = args[index + 1] || "";
|
|
97
|
+
index += 1;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (arg === "--to") {
|
|
101
|
+
options.to = args[index + 1] || "";
|
|
102
|
+
index += 1;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (arg === "--query-url") {
|
|
106
|
+
options.queryUrl = args[index + 1] || "";
|
|
107
|
+
index += 1;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (arg === "--proxy") {
|
|
111
|
+
options.proxy = args[index + 1] || "";
|
|
112
|
+
index += 1;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (arg === "--workdir") {
|
|
116
|
+
options.workdir = args[index + 1] || "";
|
|
117
|
+
index += 1;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (arg === "--storage-dir") {
|
|
121
|
+
options.storageDir = args[index + 1] || "";
|
|
122
|
+
index += 1;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (arg === "--download-concurrency") {
|
|
126
|
+
options.downloadConcurrency = args[index + 1] || "";
|
|
127
|
+
index += 1;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (arg === "--limit") {
|
|
131
|
+
options.limit = args[index + 1] || "";
|
|
132
|
+
index += 1;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (arg === "--offset") {
|
|
136
|
+
options.offset = args[index + 1] || "";
|
|
137
|
+
index += 1;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (arg === "--select") {
|
|
141
|
+
options.select = args[index + 1] || "";
|
|
142
|
+
index += 1;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (arg === "--report-path") {
|
|
146
|
+
options.reportPath = args[index + 1] || "";
|
|
147
|
+
index += 1;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (arg === "--repair-plan-path") {
|
|
151
|
+
options.repairPlanPath = args[index + 1] || "";
|
|
152
|
+
index += 1;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (arg === "--repair-version") {
|
|
156
|
+
options.repairVersion = args[index + 1] || "";
|
|
157
|
+
index += 1;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (arg === "--plan-file") {
|
|
161
|
+
options.planFile = args[index + 1] || "";
|
|
162
|
+
index += 1;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (arg === "--plan-text") {
|
|
166
|
+
options.planText = args[index + 1] || "";
|
|
167
|
+
index += 1;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return { help: false, options };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
module.exports = {
|
|
175
|
+
printCliUsage,
|
|
176
|
+
parseCliArgs,
|
|
177
|
+
};
|
package/lib/cli/run.js
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
const fs = require("node:fs/promises");
|
|
2
|
+
const { createOptions, runBacktraceCommand, printList } = require("../BacktraceCodexTool");
|
|
3
|
+
const { applyFixFromFingerprint, generateRepairPlanFromFingerprint } = require("../backtrace/repair-fingerprint");
|
|
4
|
+
|
|
5
|
+
// 把 CLI 原始字符串参数转换成底层 Backtrace 工具共用的标准选项结构。
|
|
6
|
+
function buildBacktraceOverrides(rawOptions, command) {
|
|
7
|
+
const overrides = { command };
|
|
8
|
+
// 这里只复制用户显式传入的字段,避免把空字符串覆盖默认值。
|
|
9
|
+
if (rawOptions.fingerprint) {
|
|
10
|
+
overrides.fingerprint = rawOptions.fingerprint;
|
|
11
|
+
}
|
|
12
|
+
if (rawOptions.objectId) {
|
|
13
|
+
overrides.objectId = rawOptions.objectId;
|
|
14
|
+
}
|
|
15
|
+
if (rawOptions.from) {
|
|
16
|
+
overrides.from = rawOptions.from;
|
|
17
|
+
}
|
|
18
|
+
if (rawOptions.to) {
|
|
19
|
+
overrides.to = rawOptions.to;
|
|
20
|
+
}
|
|
21
|
+
if (rawOptions.queryUrl) {
|
|
22
|
+
overrides.queryUrl = rawOptions.queryUrl;
|
|
23
|
+
}
|
|
24
|
+
if (rawOptions.proxy) {
|
|
25
|
+
overrides.proxy = rawOptions.proxy;
|
|
26
|
+
}
|
|
27
|
+
if (rawOptions.workdir) {
|
|
28
|
+
overrides.workdir = rawOptions.workdir;
|
|
29
|
+
}
|
|
30
|
+
if (rawOptions.storageDir) {
|
|
31
|
+
overrides.storageDir = rawOptions.storageDir;
|
|
32
|
+
}
|
|
33
|
+
if (rawOptions.downloadConcurrency) {
|
|
34
|
+
overrides.downloadConcurrency = Number(rawOptions.downloadConcurrency);
|
|
35
|
+
}
|
|
36
|
+
if (rawOptions.limit) {
|
|
37
|
+
overrides.limit = Number(rawOptions.limit);
|
|
38
|
+
}
|
|
39
|
+
if (rawOptions.offset) {
|
|
40
|
+
overrides.offset = Number(rawOptions.offset);
|
|
41
|
+
}
|
|
42
|
+
if (rawOptions.select) {
|
|
43
|
+
overrides.select = rawOptions.select;
|
|
44
|
+
}
|
|
45
|
+
return createOptions(overrides);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// CLI 默认输出结构化 JSON,便于管道处理和人工查看。
|
|
49
|
+
function printJson(data) {
|
|
50
|
+
console.log(JSON.stringify(data, null, 2));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function formatUnixTimestamp(value) {
|
|
54
|
+
const numeric = Number(value);
|
|
55
|
+
if (!Number.isFinite(numeric) || numeric <= 0) {
|
|
56
|
+
return String(value || "");
|
|
57
|
+
}
|
|
58
|
+
return `${numeric} (${new Date(numeric * 1000).toISOString()})`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function formatObjectTimestamp(value) {
|
|
62
|
+
const numeric = Number(value);
|
|
63
|
+
if (!Number.isFinite(numeric) || numeric <= 0) {
|
|
64
|
+
return String(value || "");
|
|
65
|
+
}
|
|
66
|
+
const date = new Date(numeric * 1000);
|
|
67
|
+
const year = String(date.getFullYear());
|
|
68
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
69
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
70
|
+
const hour = String(date.getHours()).padStart(2, "0");
|
|
71
|
+
const minute = String(date.getMinutes()).padStart(2, "0");
|
|
72
|
+
const second = String(date.getSeconds()).padStart(2, "0");
|
|
73
|
+
return `${year}-${month}-${day} ${hour}:${minute}:${second} (${numeric})`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function truncateLine(value, maxLength = 200) {
|
|
77
|
+
const text = String(value || "").replace(/\s+/g, " ").trim();
|
|
78
|
+
if (text.length <= maxLength) {
|
|
79
|
+
return text;
|
|
80
|
+
}
|
|
81
|
+
return `${text.slice(0, maxLength)}...`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function printFingerprintDetail(result) {
|
|
85
|
+
console.log(`Fingerprint: ${result.fingerprint}`);
|
|
86
|
+
console.log(`First Seen: ${formatUnixTimestamp(result.firstSeen)}`);
|
|
87
|
+
console.log(`Issue State: ${result.issueState || ""}`);
|
|
88
|
+
console.log(`Classifiers: ${result.classifiers || ""}`);
|
|
89
|
+
console.log(`Object Count: ${result.objectCount}`);
|
|
90
|
+
console.log(`Query Total Rows: ${result.querySummary?.totalRows ?? result.objectCount}`);
|
|
91
|
+
console.log(`Fetched Objects: ${result.querySummary?.fetchedRows ?? result.objectCount}`);
|
|
92
|
+
console.log(`Page Limit: ${result.querySummary?.pageLimit ?? ""}`);
|
|
93
|
+
console.log(`Error Message: ${truncateLine(result.errorMessage, 240)}`);
|
|
94
|
+
console.log("");
|
|
95
|
+
console.log("Objects:");
|
|
96
|
+
|
|
97
|
+
if (!Array.isArray(result.objects) || result.objects.length === 0) {
|
|
98
|
+
console.log("No objects found.");
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
result.objects.forEach((item, index) => {
|
|
103
|
+
console.log(`${index + 1}. ${item.objectIdHex} ${formatObjectTimestamp(item.timestamp)}`);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function printLocalFingerprintList(items) {
|
|
108
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
109
|
+
console.log("No local fingerprints found.");
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
items.forEach((item, index) => {
|
|
114
|
+
console.log(`${index + 1}. ${item.fingerprint}`);
|
|
115
|
+
console.log(` State: ${item.issueState || ""}`);
|
|
116
|
+
console.log(` Error: ${truncateLine(item.errorMessage, 200)}`);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 根据解析后的命令分发到对应的 Backtrace 或修复流程。
|
|
121
|
+
async function runCliCommand(rawOptions) {
|
|
122
|
+
if (rawOptions.command === "list") {
|
|
123
|
+
const options = buildBacktraceOverrides(rawOptions, "list");
|
|
124
|
+
const result = await runBacktraceCommand("list", options);
|
|
125
|
+
printLocalFingerprintList(result.items);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (rawOptions.command === "fingerprint") {
|
|
130
|
+
// fingerprint 只查远端并打印指纹列表,不涉及本地分析和报告生成。
|
|
131
|
+
const options = buildBacktraceOverrides(rawOptions, "fingerprint");
|
|
132
|
+
const result = await runBacktraceCommand("fingerprint", options);
|
|
133
|
+
if (result.mode === "detail") {
|
|
134
|
+
printFingerprintDetail(result);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
printList(result.items, {
|
|
138
|
+
from: options.from,
|
|
139
|
+
to: options.to,
|
|
140
|
+
totalRows: result.querySummary.totalRows,
|
|
141
|
+
totalListed: result.querySummary.fetchedRows,
|
|
142
|
+
objectCount: result.querySummary.objectCount,
|
|
143
|
+
pageLimit: result.querySummary.pageLimit,
|
|
144
|
+
});
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (rawOptions.command === "collect") {
|
|
149
|
+
// collect 只做下载,不生成分析报告。
|
|
150
|
+
const options = buildBacktraceOverrides(rawOptions, "collect-all");
|
|
151
|
+
const result = await runBacktraceCommand("collect-all", options);
|
|
152
|
+
printJson({
|
|
153
|
+
command: "collect",
|
|
154
|
+
fingerprint: options.fingerprint || "",
|
|
155
|
+
downloadedCount: result.downloadedItems.length,
|
|
156
|
+
storageDir: options.storageDir,
|
|
157
|
+
});
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (rawOptions.command === "summarize-errors") {
|
|
162
|
+
const options = buildBacktraceOverrides(rawOptions, "summarize-fingerprint-errors");
|
|
163
|
+
const result = await runBacktraceCommand("summarize-fingerprint-errors", options);
|
|
164
|
+
printJson(result);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (rawOptions.command === "fix-plan") {
|
|
169
|
+
if (!rawOptions.fingerprint) {
|
|
170
|
+
throw new Error("--fingerprint is required for fix-plan");
|
|
171
|
+
}
|
|
172
|
+
const result = await generateRepairPlanFromFingerprint({ fingerprint: rawOptions.fingerprint }, rawOptions);
|
|
173
|
+
printJson(result);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (rawOptions.command === "apply-fix") {
|
|
178
|
+
if (!rawOptions.fingerprint) {
|
|
179
|
+
throw new Error("--fingerprint is required for apply-fix");
|
|
180
|
+
}
|
|
181
|
+
const result = await applyFixFromFingerprint({ fingerprint: rawOptions.fingerprint }, rawOptions);
|
|
182
|
+
printJson(result);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
throw new Error("Unsupported command. Use --help to see available commands.");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
module.exports = {
|
|
190
|
+
runCliCommand,
|
|
191
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "backtrace-console",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "CLI for querying Backtrace fingerprints, collecting artifacts, and generating repair plans.",
|
|
5
|
+
"private": false,
|
|
6
|
+
"files": [
|
|
7
|
+
"app.js",
|
|
8
|
+
"bin",
|
|
9
|
+
"lib",
|
|
10
|
+
"public",
|
|
11
|
+
"routes"
|
|
12
|
+
],
|
|
13
|
+
"bin": {
|
|
14
|
+
"backtrace": "bin/backtrace-cli.js"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"start": "node ./bin/www",
|
|
18
|
+
"backtrace": "node ./bin/backtrace-cli.js"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@openai/codex-sdk": "^0.118.0",
|
|
22
|
+
"cookie-parser": "~1.4.4",
|
|
23
|
+
"debug": "~2.6.9",
|
|
24
|
+
"dotenv": "^17.4.1",
|
|
25
|
+
"express": "~4.16.1",
|
|
26
|
+
"morgan": "~1.9.1",
|
|
27
|
+
"undici": "^7.24.8"
|
|
28
|
+
}
|
|
29
|
+
}
|