opencode-metrics-plugin 0.2.0 → 0.3.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/CHANGELOG.md +16 -0
- package/README.md +80 -53
- package/assets/agc-apiclient.json +10 -0
- package/dist/api.d.ts +3 -0
- package/dist/api.js +4 -0
- package/dist/index.d.ts +0 -3
- package/dist/index.js +2 -3
- package/dist/metrics/configLoader.d.ts +20 -0
- package/dist/metrics/configLoader.js +99 -0
- package/dist/metrics/dirs.d.ts +2 -0
- package/dist/metrics/dirs.js +1 -0
- package/dist/metrics/engine/engine.d.ts +8 -4
- package/dist/metrics/engine/engine.js +105 -27
- package/dist/metrics/engine/state.d.ts +17 -0
- package/dist/metrics/engine/state.js +2 -0
- package/dist/metrics/index.d.ts +3 -0
- package/dist/metrics/index.js +1 -0
- package/dist/metrics/runtime.d.ts +4 -0
- package/dist/metrics/runtime.js +10 -0
- package/dist/metrics/snapshot/flush.d.ts +17 -0
- package/dist/metrics/snapshot/flush.js +34 -4
- package/dist/metrics/snapshot/merge.d.ts +10 -2
- package/dist/metrics/snapshot/merge.js +47 -14
- package/dist/metrics/snapshot/steps.d.ts +15 -1
- package/dist/metrics/snapshot/steps.js +112 -48
- package/dist/metrics/upload/agcUploader.d.ts +65 -0
- package/dist/metrics/upload/agcUploader.js +244 -0
- package/dist/metrics/upload/packageArchiver.d.ts +34 -0
- package/dist/metrics/upload/packageArchiver.js +213 -0
- package/dist/metrics/upload/scenarios.d.ts +20 -0
- package/dist/metrics/upload/scenarios.js +43 -0
- package/dist/metrics/upload/types.d.ts +111 -0
- package/dist/metrics/upload/types.js +43 -0
- package/dist/metrics/upload/uploadManager.d.ts +25 -0
- package/dist/metrics/upload/uploadManager.js +252 -0
- package/dist/metrics/upload/uploadQueue.d.ts +34 -0
- package/dist/metrics/upload/uploadQueue.js +77 -0
- package/dist/metrics/upload/workspaceResolver.d.ts +75 -0
- package/dist/metrics/upload/workspaceResolver.js +275 -0
- package/dist/plugin.d.ts +8 -5
- package/dist/plugin.js +12 -7
- package/dist/shared/log.d.ts +0 -1
- package/dist/shared/log.js +0 -3
- package/package.json +8 -2
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { DEFAULT_DIR_MARKERS, DEFAULT_FILE_MARKERS, MAX_PROBE_DEPTH, MAX_ROOTS, sanitizeNameSegment, } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* 会话证据收集器:旁路记录 write/edit 的文件路径与 bash 的工作目录、命令路径线索。
|
|
6
|
+
* 触发上传时用这些路径推断产物根。
|
|
7
|
+
*/
|
|
8
|
+
export class EvidenceCollector {
|
|
9
|
+
filePaths = new Set();
|
|
10
|
+
directories = new Set();
|
|
11
|
+
/**
|
|
12
|
+
* 记录一次文件写入/编辑。
|
|
13
|
+
*
|
|
14
|
+
* @param filePath 被写入文件的绝对路径
|
|
15
|
+
*/
|
|
16
|
+
recordFileEdit(filePath) {
|
|
17
|
+
if (!filePath)
|
|
18
|
+
return;
|
|
19
|
+
this.filePaths.add(path.resolve(filePath));
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 记录一次 bash 调用:登记工作目录,并从命令行提取路径线索
|
|
23
|
+
* (cd <path>、git -C <path>、出现 hvigorw 时登记工作目录本身)。
|
|
24
|
+
*
|
|
25
|
+
* @param workdir 命令执行目录(可能为空)
|
|
26
|
+
* @param command 完整命令行
|
|
27
|
+
*/
|
|
28
|
+
recordBash(workdir, command) {
|
|
29
|
+
const base = workdir ? path.resolve(workdir) : process.cwd();
|
|
30
|
+
this.directories.add(base);
|
|
31
|
+
if (!command)
|
|
32
|
+
return;
|
|
33
|
+
// cd <path>(含引号形式),相对路径基于工作目录
|
|
34
|
+
const cdRegex = /(?:^|[\s;&(])cd\s+(?:"([^"&;]+)"|'([^'&;]+)'|([^\s;&|]+))/g;
|
|
35
|
+
let match;
|
|
36
|
+
while ((match = cdRegex.exec(command)) !== null) {
|
|
37
|
+
const target = match[1] ?? match[2] ?? match[3];
|
|
38
|
+
if (target)
|
|
39
|
+
this.directories.add(this.resolveAgainst(target, base));
|
|
40
|
+
}
|
|
41
|
+
// git -C <path>
|
|
42
|
+
const gitCRegex = /\bgit\s+(?:-\w+\s+)*-C\s+(?:"([^"&;]+)"|([^\s;&|]+))/g;
|
|
43
|
+
while ((match = gitCRegex.exec(command)) !== null) {
|
|
44
|
+
const target = match[1] ?? match[2];
|
|
45
|
+
if (target)
|
|
46
|
+
this.directories.add(this.resolveAgainst(target, base));
|
|
47
|
+
}
|
|
48
|
+
// hvigorw 出现即认为工作目录是构建现场
|
|
49
|
+
if (/hvigorw/.test(command)) {
|
|
50
|
+
this.directories.add(base);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** 全部证据路径(文件 + 目录),供根推断使用 */
|
|
54
|
+
seeds() {
|
|
55
|
+
return [...this.filePaths, ...this.directories];
|
|
56
|
+
}
|
|
57
|
+
resolveAgainst(target, base) {
|
|
58
|
+
if (path.isAbsolute(target))
|
|
59
|
+
return path.resolve(target);
|
|
60
|
+
return path.resolve(base, target);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 判断目录是否为安卓工程:统计 gradle 构建信号(settings.gradle、gradlew、
|
|
65
|
+
* app/build.gradle、根 build.gradle+app/、AndroidManifest+src/),
|
|
66
|
+
* 命中 2 个及以上即判定,避免单文件误判。
|
|
67
|
+
*
|
|
68
|
+
* @param directory 待判定目录
|
|
69
|
+
* @returns 是安卓工程返回 true
|
|
70
|
+
*/
|
|
71
|
+
export function looksLikeAndroidProject(directory) {
|
|
72
|
+
const exists = (name) => fs.existsSync(path.join(directory, name));
|
|
73
|
+
let signals = 0;
|
|
74
|
+
if (exists("settings.gradle") || exists("settings.gradle.kts"))
|
|
75
|
+
signals++;
|
|
76
|
+
if (exists("gradlew") || exists("gradlew.bat"))
|
|
77
|
+
signals++;
|
|
78
|
+
if (exists("app/build.gradle") || exists("app/build.gradle.kts"))
|
|
79
|
+
signals++;
|
|
80
|
+
if ((exists("build.gradle") || exists("build.gradle.kts")) && exists("app"))
|
|
81
|
+
signals++;
|
|
82
|
+
if (exists("AndroidManifest.xml") && exists("src"))
|
|
83
|
+
signals++;
|
|
84
|
+
return signals >= 2;
|
|
85
|
+
}
|
|
86
|
+
/** 判断目录是否含鸿蒙工程标记(oh-package.json5 / build-profile.json5 / hvigorfile.ts / AppScope) */
|
|
87
|
+
function hasHarmonyProjectMarkers(directory) {
|
|
88
|
+
return ["oh-package.json5", "build-profile.json5", "hvigorfile.ts", "AppScope"].some((marker) => fs.existsSync(path.join(directory, marker)));
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* 从证据路径推断产物根:自每条证据所在目录向上逐级探测标记(最多 5 层),
|
|
92
|
+
* 目录内存在任一标记即记为根;安卓工程根(自身无鸿蒙工程标记)先剔除,
|
|
93
|
+
* 嵌套根归并到最外层,去重后最多 3 个。
|
|
94
|
+
*
|
|
95
|
+
* @param seeds 证据路径(文件或目录的绝对路径)
|
|
96
|
+
* @param options 追加标记与安卓剔除开关(来自 appendArtifacts 配置)
|
|
97
|
+
* @returns 产物根绝对路径数组;无命中返回空数组
|
|
98
|
+
*/
|
|
99
|
+
export function resolveWorkspaceRoots(seeds, options) {
|
|
100
|
+
const dirMarkers = [...DEFAULT_DIR_MARKERS, ...(options?.extraDirMarkers ?? [])];
|
|
101
|
+
const fileMarkers = [...DEFAULT_FILE_MARKERS, ...(options?.extraFileMarkers ?? [])];
|
|
102
|
+
const markers = [...dirMarkers, ...fileMarkers];
|
|
103
|
+
const found = [];
|
|
104
|
+
for (const seed of seeds) {
|
|
105
|
+
const startDirectory = directoryOf(seed);
|
|
106
|
+
if (!startDirectory)
|
|
107
|
+
continue;
|
|
108
|
+
let current = startDirectory;
|
|
109
|
+
// 起始目录自身 + 向上最多 5 层祖先
|
|
110
|
+
for (let depth = 0; depth <= MAX_PROBE_DEPTH; depth++) {
|
|
111
|
+
if (markers.some((marker) => fs.existsSync(path.join(current, marker)))) {
|
|
112
|
+
found.push(current);
|
|
113
|
+
}
|
|
114
|
+
const parent = path.dirname(current);
|
|
115
|
+
if (parent === current)
|
|
116
|
+
break;
|
|
117
|
+
current = parent;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
let candidates = dedupeRoots(found);
|
|
121
|
+
if (options?.excludeAndroidProjects !== false) {
|
|
122
|
+
candidates = candidates.filter((root) => !looksLikeAndroidProject(root) || hasHarmonyProjectMarkers(root));
|
|
123
|
+
}
|
|
124
|
+
return limitRoots(mergeNestedRoots(candidates));
|
|
125
|
+
}
|
|
126
|
+
/** 取路径所在目录:目录返回自身,文件返回父目录,不存在返回 null */
|
|
127
|
+
function directoryOf(target) {
|
|
128
|
+
try {
|
|
129
|
+
const resolved = path.resolve(target);
|
|
130
|
+
const stat = fs.statSync(resolved);
|
|
131
|
+
return stat.isDirectory() ? resolved : path.dirname(resolved);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function normalizeKey(target) {
|
|
138
|
+
const resolved = path.resolve(target);
|
|
139
|
+
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
140
|
+
}
|
|
141
|
+
function dedupeRoots(roots) {
|
|
142
|
+
const seen = new Set();
|
|
143
|
+
const result = [];
|
|
144
|
+
for (const root of roots) {
|
|
145
|
+
const key = normalizeKey(root);
|
|
146
|
+
if (!seen.has(key)) {
|
|
147
|
+
seen.add(key);
|
|
148
|
+
result.push(root);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return result;
|
|
152
|
+
}
|
|
153
|
+
/** 嵌套根归并:位于其他根内部的根丢弃,只保留最外层 */
|
|
154
|
+
function mergeNestedRoots(roots) {
|
|
155
|
+
return roots.filter((root) => {
|
|
156
|
+
const rootKey = normalizeKey(root);
|
|
157
|
+
return !roots.some((other) => {
|
|
158
|
+
const otherKey = normalizeKey(other);
|
|
159
|
+
if (otherKey === rootKey)
|
|
160
|
+
return false;
|
|
161
|
+
return rootKey.startsWith(otherKey + path.sep);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
/** 根数量上限:按路径排序后截取前 MAX_ROOTS 个 */
|
|
166
|
+
function limitRoots(roots) {
|
|
167
|
+
const sorted = [...roots].sort();
|
|
168
|
+
return sorted.slice(0, MAX_ROOTS);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* 判断证据路径是否位于配置路径之下(追加产物 "session-activity" 命中判定,
|
|
172
|
+
* Windows 下大小写不敏感)。
|
|
173
|
+
*
|
|
174
|
+
* @param evidencePath 证据路径
|
|
175
|
+
* @param configPath 配置的产物路径
|
|
176
|
+
* @returns 命中返回 true
|
|
177
|
+
*/
|
|
178
|
+
export function isPathInside(evidencePath, configPath) {
|
|
179
|
+
const evidenceKey = normalizeKey(path.resolve(evidencePath));
|
|
180
|
+
const configKey = normalizeKey(path.resolve(configPath));
|
|
181
|
+
return evidenceKey === configKey || evidenceKey.startsWith(configKey + path.sep);
|
|
182
|
+
}
|
|
183
|
+
// ─── 安卓项目名推断 ───────────────────────────────────────────────────────────
|
|
184
|
+
/** 包名候选文件:相对证据目录的已知位置(祖先目录同样尝试) */
|
|
185
|
+
const ANDROID_PACKAGE_CANDIDATES = [
|
|
186
|
+
"app/build.gradle",
|
|
187
|
+
"app/build.gradle.kts",
|
|
188
|
+
"build.gradle",
|
|
189
|
+
"build.gradle.kts",
|
|
190
|
+
"app/src/main/AndroidManifest.xml",
|
|
191
|
+
"src/main/AndroidManifest.xml",
|
|
192
|
+
];
|
|
193
|
+
/** 定位候选文件时自证据目录向上查找的祖先层数 */
|
|
194
|
+
const ANDROID_ANCESTOR_DEPTH = 3;
|
|
195
|
+
/**
|
|
196
|
+
* 从会话证据推断安卓项目名:在证据目录及其祖先(≤3 层)定位
|
|
197
|
+
* build.gradle / AndroidManifest.xml,按 applicationId > manifest package > namespace
|
|
198
|
+
* 的优先级提取包名;探测不到时回退目录名,仍无则返回 null。
|
|
199
|
+
*
|
|
200
|
+
* @param seeds 证据路径(文件或目录的绝对路径)
|
|
201
|
+
* @param options fallbackDirectory 提供目录兜底
|
|
202
|
+
* @returns 包名 / 兜底目录名(经名称清洗);无可返回 null
|
|
203
|
+
*/
|
|
204
|
+
export function resolveAndroidProjectName(seeds, options) {
|
|
205
|
+
const candidateFiles = locateAndroidFiles(seeds);
|
|
206
|
+
// groovy 语法 applicationId "x" 无等号,kts 语法 applicationId = "x" 有等号,[=:]? 兼容两者
|
|
207
|
+
const packageId = extractByPattern(candidateFiles.gradle, /applicationId\s*[=:]?\s*["']([^"']+)["']/);
|
|
208
|
+
if (packageId)
|
|
209
|
+
return sanitizeNameSegment(packageId);
|
|
210
|
+
const manifestPackage = extractByPattern(candidateFiles.manifest, /\bpackage\s*=\s*"([^"]+)"/);
|
|
211
|
+
if (manifestPackage)
|
|
212
|
+
return sanitizeNameSegment(manifestPackage);
|
|
213
|
+
const namespace = extractByPattern(candidateFiles.gradle, /namespace\s*[=:]?\s*["']([^"']+)["']/);
|
|
214
|
+
if (namespace)
|
|
215
|
+
return sanitizeNameSegment(namespace);
|
|
216
|
+
if (options?.fallbackDirectory) {
|
|
217
|
+
return sanitizeNameSegment(path.basename(path.resolve(options.fallbackDirectory)));
|
|
218
|
+
}
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
/** 在证据目录及其祖先中定位全部存在的候选文件,按路径由浅到深分组返回 */
|
|
222
|
+
function locateAndroidFiles(seeds) {
|
|
223
|
+
const gradle = [];
|
|
224
|
+
const manifest = [];
|
|
225
|
+
const seen = new Set();
|
|
226
|
+
for (const seed of seeds) {
|
|
227
|
+
const startDirectory = directoryOf(seed);
|
|
228
|
+
if (!startDirectory)
|
|
229
|
+
continue;
|
|
230
|
+
let current = startDirectory;
|
|
231
|
+
for (let depth = 0; depth <= ANDROID_ANCESTOR_DEPTH; depth++) {
|
|
232
|
+
for (const candidate of ANDROID_PACKAGE_CANDIDATES) {
|
|
233
|
+
const filePath = path.join(current, candidate);
|
|
234
|
+
const key = normalizeKey(filePath);
|
|
235
|
+
if (seen.has(key))
|
|
236
|
+
continue;
|
|
237
|
+
try {
|
|
238
|
+
fs.statSync(filePath);
|
|
239
|
+
seen.add(key);
|
|
240
|
+
if (filePath.endsWith(".gradle") || filePath.endsWith(".gradle.kts")) {
|
|
241
|
+
gradle.push(filePath);
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
manifest.push(filePath);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
// 不存在,跳过
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const parent = path.dirname(current);
|
|
252
|
+
if (parent === current)
|
|
253
|
+
break;
|
|
254
|
+
current = parent;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
// 浅层优先:多工程场景取最外层命中
|
|
258
|
+
const byDepth = (a, b) => a.split(path.sep).length - b.split(path.sep).length;
|
|
259
|
+
return { gradle: gradle.sort(byDepth), manifest: manifest.sort(byDepth) };
|
|
260
|
+
}
|
|
261
|
+
/** 逐文件读取并按正则提取第一个捕获组(只读文件头部,包名声明都在前部) */
|
|
262
|
+
function extractByPattern(files, pattern) {
|
|
263
|
+
for (const file of files) {
|
|
264
|
+
try {
|
|
265
|
+
const content = fs.readFileSync(file, "utf-8").slice(0, 64 * 1024);
|
|
266
|
+
const match = content.match(pattern);
|
|
267
|
+
if (match)
|
|
268
|
+
return match[1];
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
// 读取失败,尝试下一个
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return null;
|
|
275
|
+
}
|
package/dist/plugin.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { Plugin } from "@opencode-ai/plugin";
|
|
2
2
|
import type { MetricsRuntimeOptions } from "./metrics/runtime.js";
|
|
3
3
|
/**
|
|
4
|
-
* opencode
|
|
5
|
-
*
|
|
4
|
+
* opencode 插件配置项来源(优先级从高到低):
|
|
5
|
+
* ① 项目 .opencode/opencode-metrics.json(.jsonc)(存在则完全接管,支持注释);
|
|
6
|
+
* ② opencode.json 插件内联第二参(本类型,向后兼容)。
|
|
7
|
+
* 字段同 MetricsRuntimeOptions:enabled / eventLogging / checkpointFlushIntervalMs / dirs / upload。
|
|
6
8
|
*/
|
|
7
9
|
export type MetricsPluginOptions = MetricsRuntimeOptions;
|
|
8
10
|
/**
|
|
@@ -11,12 +13,13 @@ export type MetricsPluginOptions = MetricsRuntimeOptions;
|
|
|
11
13
|
* ```jsonc
|
|
12
14
|
* // opencode.json
|
|
13
15
|
* { "plugin": ["opencode-metrics-plugin"] }
|
|
14
|
-
* //
|
|
15
|
-
* { "plugin": [["opencode-metrics-plugin", { "enabled": true, "
|
|
16
|
+
* // 带配置(内联,或移入项目根 opencode-metrics.json):
|
|
17
|
+
* { "plugin": [["opencode-metrics-plugin", { "enabled": true, "upload": { "enabled": true } }]] }
|
|
16
18
|
* ```
|
|
17
19
|
*
|
|
18
20
|
* 挂上后自动记录全部会话:每轮 session.idle 落盘快照 JSON,
|
|
19
|
-
* dispose 时兜底 flush
|
|
21
|
+
* dispose 时兜底 flush 未落盘会话并排空上传队列。
|
|
22
|
+
* @opencode-ai/plugin 仅作类型依赖(零运行时依赖)。
|
|
20
23
|
*/
|
|
21
24
|
export declare const MetricsPlugin: Plugin;
|
|
22
25
|
export default MetricsPlugin;
|
package/dist/plugin.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createMetricsRuntime } from "./metrics/runtime.js";
|
|
2
|
+
import { resolvePluginConfig } from "./metrics/configLoader.js";
|
|
2
3
|
import { log } from "./shared/log.js";
|
|
3
4
|
/**
|
|
4
5
|
* opencode 插件入口。
|
|
@@ -6,20 +7,23 @@ import { log } from "./shared/log.js";
|
|
|
6
7
|
* ```jsonc
|
|
7
8
|
* // opencode.json
|
|
8
9
|
* { "plugin": ["opencode-metrics-plugin"] }
|
|
9
|
-
* //
|
|
10
|
-
* { "plugin": [["opencode-metrics-plugin", { "enabled": true, "
|
|
10
|
+
* // 带配置(内联,或移入项目根 opencode-metrics.json):
|
|
11
|
+
* { "plugin": [["opencode-metrics-plugin", { "enabled": true, "upload": { "enabled": true } }]] }
|
|
11
12
|
* ```
|
|
12
13
|
*
|
|
13
14
|
* 挂上后自动记录全部会话:每轮 session.idle 落盘快照 JSON,
|
|
14
|
-
* dispose 时兜底 flush
|
|
15
|
+
* dispose 时兜底 flush 未落盘会话并排空上传队列。
|
|
16
|
+
* @opencode-ai/plugin 仅作类型依赖(零运行时依赖)。
|
|
15
17
|
*/
|
|
16
|
-
export const MetricsPlugin = async (
|
|
17
|
-
const
|
|
18
|
-
const runtime = createMetricsRuntime(
|
|
18
|
+
export const MetricsPlugin = async (input, options) => {
|
|
19
|
+
const { config, source } = resolvePluginConfig(input.directory, (options ?? {}));
|
|
20
|
+
const runtime = createMetricsRuntime(config);
|
|
19
21
|
log.info("[MetricsPlugin] started", {
|
|
20
22
|
metricsDir: runtime.dirs.metricsDir,
|
|
21
23
|
eventsDir: runtime.dirs.eventsDir,
|
|
22
|
-
enabled:
|
|
24
|
+
enabled: config.enabled !== false,
|
|
25
|
+
upload: Boolean(runtime.uploadManager),
|
|
26
|
+
configSource: source,
|
|
23
27
|
});
|
|
24
28
|
const hooks = {
|
|
25
29
|
event: async ({ event }) => {
|
|
@@ -41,6 +45,7 @@ export const MetricsPlugin = async (_input, options) => {
|
|
|
41
45
|
dispose: async () => {
|
|
42
46
|
runtime.eventLogger?.dispose();
|
|
43
47
|
runtime.engine.dispose();
|
|
48
|
+
await runtime.uploadManager?.dispose();
|
|
44
49
|
log.info("[MetricsPlugin] disposed");
|
|
45
50
|
},
|
|
46
51
|
};
|
package/dist/shared/log.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
2
2
|
export declare function setLogLevel(level: LogLevel): void;
|
|
3
|
-
export declare function getLogLevel(): LogLevel;
|
|
4
3
|
/** 设置运行日志落盘文件;传 null 关闭文件输出(仅 console)。 */
|
|
5
4
|
export declare function setLogFile(file: string | null): void;
|
|
6
5
|
export declare function flushLogs(): void;
|
package/dist/shared/log.js
CHANGED
|
@@ -6,9 +6,6 @@ let minOrder = LEVEL_ORDER.info;
|
|
|
6
6
|
export function setLogLevel(level) {
|
|
7
7
|
minOrder = LEVEL_ORDER[level];
|
|
8
8
|
}
|
|
9
|
-
export function getLogLevel() {
|
|
10
|
-
return Object.keys(LEVEL_ORDER).find((k) => LEVEL_ORDER[k] === minOrder) ?? 'info';
|
|
11
|
-
}
|
|
12
9
|
const DEFAULT_LOG_FILE = path.join(envPaths('opencode-metrics-plugin', { suffix: '' }).log, 'plugin.log');
|
|
13
10
|
let logFile = DEFAULT_LOG_FILE;
|
|
14
11
|
/** 设置运行日志落盘文件;传 null 关闭文件输出(仅 console)。 */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-metrics-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "opencode plugin: auto-records every session's metrics (tokens/rounds/steps/sub-agents/hvigor builds) to snapshot JSON. Zero DB, zero config. Pair with session-viewer for visualization.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -17,11 +17,17 @@
|
|
|
17
17
|
".": {
|
|
18
18
|
"types": "./dist/index.d.ts",
|
|
19
19
|
"import": "./dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./api": {
|
|
22
|
+
"types": "./dist/api.d.ts",
|
|
23
|
+
"import": "./dist/api.js"
|
|
20
24
|
}
|
|
21
25
|
},
|
|
22
26
|
"files": [
|
|
23
27
|
"dist",
|
|
24
|
-
"
|
|
28
|
+
"assets",
|
|
29
|
+
"README.md",
|
|
30
|
+
"CHANGELOG.md"
|
|
25
31
|
],
|
|
26
32
|
"scripts": {
|
|
27
33
|
"build": "node -e \"fs.rmSync('dist', { recursive: true, force: true })\" && tsc",
|