@spzhongwin/skill-logger-plugin 1.0.11 → 1.0.13
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/active-skills.js +67 -0
- package/dist/active-skills.test.js +29 -0
- package/dist/config-sync.js +439 -0
- package/dist/config-sync.test.js +145 -0
- package/dist/hooks.js +337 -0
- package/dist/hooks.test.js +123 -0
- package/dist/http.js +54 -0
- package/dist/identity.js +56 -0
- package/dist/index.js +240 -78
- package/dist/index.test.js +39 -0
- package/dist/integration.test.js +102 -0
- package/dist/matcher.js +362 -0
- package/dist/matcher.test.js +139 -0
- package/dist/paths.js +62 -0
- package/dist/paths.test.js +49 -0
- package/dist/reporter.js +267 -0
- package/dist/reporter.test.js +128 -0
- package/dist/semver.js +64 -0
- package/dist/semver.test.js +21 -0
- package/dist/skill-version.js +23 -0
- package/dist/types.js +9 -0
- package/dist/updater.js +352 -0
- package/dist/updater.test.js +212 -0
- package/dist/ws-client.js +484 -0
- package/openclaw.plugin.json +50 -50
- package/package.json +37 -37
- package/src/active-skills.test.ts +32 -32
- package/src/active-skills.ts +77 -77
- package/src/config-sync.test.ts +165 -165
- package/src/config-sync.ts +544 -544
- package/src/hooks.test.ts +251 -251
- package/src/hooks.ts +517 -517
- package/src/http.ts +61 -61
- package/src/identity.ts +64 -64
- package/src/index.test.ts +53 -53
- package/src/index.ts +226 -226
- package/src/integration.test.ts +119 -119
- package/src/matcher.test.ts +170 -170
- package/src/matcher.ts +393 -393
- package/src/paths.test.ts +57 -57
- package/src/paths.ts +84 -84
- package/src/reporter.test.ts +139 -139
- package/src/reporter.ts +298 -298
- package/src/sample-config.json +72 -72
- package/src/semver.test.ts +23 -23
- package/src/semver.ts +60 -60
- package/src/skill-version.ts +53 -53
- package/src/types.ts +198 -198
- package/src/updater.test.ts +325 -237
- package/src/updater.ts +549 -433
- package/src/ws-client.test.ts +48 -37
- package/src/ws-client.ts +717 -642
- package/test-ws.ts +17 -17
- package/tsconfig.json +14 -14
package/src/http.ts
CHANGED
|
@@ -1,61 +1,61 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 默认 HTTP 客户端:使用 Node 原生 https 模块发起请求,
|
|
3
|
-
* 显式关闭 SSL 证书校验 (rejectUnauthorized: false),并支持超时。
|
|
4
|
-
* 避免自签证书导致的 UNABLE_TO_VERIFY_LEAF_SIGNATURE 异常。
|
|
5
|
-
*/
|
|
6
|
-
import https from "node:https";
|
|
7
|
-
import http from "node:http";
|
|
8
|
-
import { URL } from "node:url";
|
|
9
|
-
|
|
10
|
-
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
11
|
-
|
|
12
|
-
export function defaultFetch(timeoutMs: number = DEFAULT_TIMEOUT_MS) {
|
|
13
|
-
return (urlStr: string, init: RequestInit): Promise<any> => {
|
|
14
|
-
return new Promise((resolve, reject) => {
|
|
15
|
-
let parsedUrl: URL;
|
|
16
|
-
try {
|
|
17
|
-
parsedUrl = new URL(urlStr);
|
|
18
|
-
} catch (err) {
|
|
19
|
-
return reject(err);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const isHttps = parsedUrl.protocol === "https:";
|
|
23
|
-
const requestFn = isHttps ? https.request : http.request;
|
|
24
|
-
|
|
25
|
-
const headers = { ...(init.headers as any) };
|
|
26
|
-
if (init.body) {
|
|
27
|
-
headers["Content-Length"] = Buffer.byteLength(init.body as string);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const options: https.RequestOptions = {
|
|
31
|
-
method: init.method || "GET",
|
|
32
|
-
headers,
|
|
33
|
-
timeout: timeoutMs,
|
|
34
|
-
rejectUnauthorized: false // <--- 核心改动:跳过 SSL 校验
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const req = requestFn(parsedUrl, options, (res) => {
|
|
38
|
-
let body = "";
|
|
39
|
-
res.on("data", chunk => { body += chunk; });
|
|
40
|
-
res.on("end", () => {
|
|
41
|
-
resolve({
|
|
42
|
-
ok: res.statusCode ? res.statusCode >= 200 && res.statusCode < 300 : false,
|
|
43
|
-
status: res.statusCode || 0,
|
|
44
|
-
text: async () => body,
|
|
45
|
-
json: async () => JSON.parse(body)
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
req.on("error", reject);
|
|
51
|
-
req.on("timeout", () => {
|
|
52
|
-
req.destroy(new Error("Timeout"));
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
if (init.body) {
|
|
56
|
-
req.write(init.body);
|
|
57
|
-
}
|
|
58
|
-
req.end();
|
|
59
|
-
});
|
|
60
|
-
};
|
|
61
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* 默认 HTTP 客户端:使用 Node 原生 https 模块发起请求,
|
|
3
|
+
* 显式关闭 SSL 证书校验 (rejectUnauthorized: false),并支持超时。
|
|
4
|
+
* 避免自签证书导致的 UNABLE_TO_VERIFY_LEAF_SIGNATURE 异常。
|
|
5
|
+
*/
|
|
6
|
+
import https from "node:https";
|
|
7
|
+
import http from "node:http";
|
|
8
|
+
import { URL } from "node:url";
|
|
9
|
+
|
|
10
|
+
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
11
|
+
|
|
12
|
+
export function defaultFetch(timeoutMs: number = DEFAULT_TIMEOUT_MS) {
|
|
13
|
+
return (urlStr: string, init: RequestInit): Promise<any> => {
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
let parsedUrl: URL;
|
|
16
|
+
try {
|
|
17
|
+
parsedUrl = new URL(urlStr);
|
|
18
|
+
} catch (err) {
|
|
19
|
+
return reject(err);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const isHttps = parsedUrl.protocol === "https:";
|
|
23
|
+
const requestFn = isHttps ? https.request : http.request;
|
|
24
|
+
|
|
25
|
+
const headers = { ...(init.headers as any) };
|
|
26
|
+
if (init.body) {
|
|
27
|
+
headers["Content-Length"] = Buffer.byteLength(init.body as string);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const options: https.RequestOptions = {
|
|
31
|
+
method: init.method || "GET",
|
|
32
|
+
headers,
|
|
33
|
+
timeout: timeoutMs,
|
|
34
|
+
rejectUnauthorized: false // <--- 核心改动:跳过 SSL 校验
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const req = requestFn(parsedUrl, options, (res) => {
|
|
38
|
+
let body = "";
|
|
39
|
+
res.on("data", chunk => { body += chunk; });
|
|
40
|
+
res.on("end", () => {
|
|
41
|
+
resolve({
|
|
42
|
+
ok: res.statusCode ? res.statusCode >= 200 && res.statusCode < 300 : false,
|
|
43
|
+
status: res.statusCode || 0,
|
|
44
|
+
text: async () => body,
|
|
45
|
+
json: async () => JSON.parse(body)
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
req.on("error", reject);
|
|
51
|
+
req.on("timeout", () => {
|
|
52
|
+
req.destroy(new Error("Timeout"));
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
if (init.body) {
|
|
56
|
+
req.write(init.body);
|
|
57
|
+
}
|
|
58
|
+
req.end();
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
}
|
package/src/identity.ts
CHANGED
|
@@ -1,64 +1,64 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 上报身份的「可替换预留点」。
|
|
3
|
-
*
|
|
4
|
-
* 当前默认实现读 git 全局 user.name/user.email + 机器名。将来若身份来源变化
|
|
5
|
-
* (例如改用平台 user_id / SSO),只需新增一个 IdentityProvider 实现并在装配处替换,
|
|
6
|
-
* 不影响 reporter 主流程。
|
|
7
|
-
*/
|
|
8
|
-
import os from "node:os";
|
|
9
|
-
import { execFile } from "node:child_process";
|
|
10
|
-
import { promisify } from "node:util";
|
|
11
|
-
import type { Identity } from "./types.ts";
|
|
12
|
-
|
|
13
|
-
const execFileAsync = promisify(execFile);
|
|
14
|
-
|
|
15
|
-
/** 读取一个全局 git 配置项;未配置或失败返回 ""(并打印 warning 提示如何设置)。 */
|
|
16
|
-
export async function getGitConfigValue(key: string): Promise<string> {
|
|
17
|
-
try {
|
|
18
|
-
const { stdout } = await execFileAsync("git", ["config", "--global", key]);
|
|
19
|
-
return stdout.trim();
|
|
20
|
-
} catch {
|
|
21
|
-
console.warn(
|
|
22
|
-
`[skill-logger-plugin] git config --global ${key} 未设置。可执行:git config --global ${key} '<value>'`
|
|
23
|
-
);
|
|
24
|
-
return "";
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface IdentityProvider {
|
|
29
|
-
getIdentity(): Promise<Identity>;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* 默认身份提供者:git 全局配置 + 机器名。
|
|
34
|
-
* 仅在「拿到真实 git name/email」后才缓存,避免把 hostname 兜底值永久固化;
|
|
35
|
-
* 若启动时尚未配置 git,会沿用原逻辑在每次上报时重试,配置生效后即被读到并缓存。
|
|
36
|
-
*/
|
|
37
|
-
export class GitIdentityProvider implements IdentityProvider {
|
|
38
|
-
private cached?: Identity;
|
|
39
|
-
|
|
40
|
-
async getIdentity(): Promise<Identity> {
|
|
41
|
-
if (this.cached) return this.cached;
|
|
42
|
-
const host = os.hostname();
|
|
43
|
-
const [rawName, rawEmail] = await Promise.all([
|
|
44
|
-
getGitConfigValue("user.name"),
|
|
45
|
-
getGitConfigValue("user.email"),
|
|
46
|
-
]);
|
|
47
|
-
|
|
48
|
-
// 过滤掉沙盒或脚手架常见的占位符
|
|
49
|
-
let finalName = rawName;
|
|
50
|
-
let finalEmail = rawEmail;
|
|
51
|
-
if (finalName === "Your Name") finalName = "";
|
|
52
|
-
if (finalEmail === "you@example.com") finalEmail = "";
|
|
53
|
-
|
|
54
|
-
const identity: Identity = {
|
|
55
|
-
user_id: "", // 预留:将来接平台用户体系时填充
|
|
56
|
-
git_name: finalName || "",
|
|
57
|
-
git_email: finalEmail || "",
|
|
58
|
-
machine_id: host,
|
|
59
|
-
};
|
|
60
|
-
// 只有拿到真正的 Git 信息才缓存,避免永久固化空值
|
|
61
|
-
if (finalName && finalEmail) this.cached = identity;
|
|
62
|
-
return identity;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* 上报身份的「可替换预留点」。
|
|
3
|
+
*
|
|
4
|
+
* 当前默认实现读 git 全局 user.name/user.email + 机器名。将来若身份来源变化
|
|
5
|
+
* (例如改用平台 user_id / SSO),只需新增一个 IdentityProvider 实现并在装配处替换,
|
|
6
|
+
* 不影响 reporter 主流程。
|
|
7
|
+
*/
|
|
8
|
+
import os from "node:os";
|
|
9
|
+
import { execFile } from "node:child_process";
|
|
10
|
+
import { promisify } from "node:util";
|
|
11
|
+
import type { Identity } from "./types.ts";
|
|
12
|
+
|
|
13
|
+
const execFileAsync = promisify(execFile);
|
|
14
|
+
|
|
15
|
+
/** 读取一个全局 git 配置项;未配置或失败返回 ""(并打印 warning 提示如何设置)。 */
|
|
16
|
+
export async function getGitConfigValue(key: string): Promise<string> {
|
|
17
|
+
try {
|
|
18
|
+
const { stdout } = await execFileAsync("git", ["config", "--global", key]);
|
|
19
|
+
return stdout.trim();
|
|
20
|
+
} catch {
|
|
21
|
+
console.warn(
|
|
22
|
+
`[skill-logger-plugin] git config --global ${key} 未设置。可执行:git config --global ${key} '<value>'`
|
|
23
|
+
);
|
|
24
|
+
return "";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface IdentityProvider {
|
|
29
|
+
getIdentity(): Promise<Identity>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 默认身份提供者:git 全局配置 + 机器名。
|
|
34
|
+
* 仅在「拿到真实 git name/email」后才缓存,避免把 hostname 兜底值永久固化;
|
|
35
|
+
* 若启动时尚未配置 git,会沿用原逻辑在每次上报时重试,配置生效后即被读到并缓存。
|
|
36
|
+
*/
|
|
37
|
+
export class GitIdentityProvider implements IdentityProvider {
|
|
38
|
+
private cached?: Identity;
|
|
39
|
+
|
|
40
|
+
async getIdentity(): Promise<Identity> {
|
|
41
|
+
if (this.cached) return this.cached;
|
|
42
|
+
const host = os.hostname();
|
|
43
|
+
const [rawName, rawEmail] = await Promise.all([
|
|
44
|
+
getGitConfigValue("user.name"),
|
|
45
|
+
getGitConfigValue("user.email"),
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
// 过滤掉沙盒或脚手架常见的占位符
|
|
49
|
+
let finalName = rawName;
|
|
50
|
+
let finalEmail = rawEmail;
|
|
51
|
+
if (finalName === "Your Name") finalName = "";
|
|
52
|
+
if (finalEmail === "you@example.com") finalEmail = "";
|
|
53
|
+
|
|
54
|
+
const identity: Identity = {
|
|
55
|
+
user_id: "", // 预留:将来接平台用户体系时填充
|
|
56
|
+
git_name: finalName || "",
|
|
57
|
+
git_email: finalEmail || "",
|
|
58
|
+
machine_id: host,
|
|
59
|
+
};
|
|
60
|
+
// 只有拿到真正的 Git 信息才缓存,避免永久固化空值
|
|
61
|
+
if (finalName && finalEmail) this.cached = identity;
|
|
62
|
+
return identity;
|
|
63
|
+
}
|
|
64
|
+
}
|
package/src/index.test.ts
CHANGED
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
import { describe, it, before } from "node:test";
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
|
|
4
|
-
let isSkillMdReadPath: (filePath: string) => boolean;
|
|
5
|
-
let extractApiPluginConfig: (api: { pluginConfig?: Record<string, unknown> }) => Record<string, unknown>;
|
|
6
|
-
|
|
7
|
-
describe("isSkillMdReadPath", () => {
|
|
8
|
-
before(async () => {
|
|
9
|
-
({ isSkillMdReadPath, extractApiPluginConfig } = await import("./index.ts"));
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
it("POSIX 路径 basename 为 SKILL.md 时为 true", () => {
|
|
13
|
-
assert.equal(isSkillMdReadPath("/x/y/my-skill/SKILL.md"), true);
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it(
|
|
17
|
-
"Windows 风格路径",
|
|
18
|
-
{ skip: process.platform !== "win32" },
|
|
19
|
-
() => {
|
|
20
|
-
assert.equal(isSkillMdReadPath(String.raw`C:\x\y\my-skill\SKILL.md`), true);
|
|
21
|
-
}
|
|
22
|
-
);
|
|
23
|
-
|
|
24
|
-
it("fooSKILL.md / my-SKILL.md 不误判", () => {
|
|
25
|
-
assert.equal(isSkillMdReadPath("/tmp/fooSKILL.md"), false);
|
|
26
|
-
assert.equal(isSkillMdReadPath("/tmp/my-SKILL.md"), false);
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
describe("extractApiPluginConfig", () => {
|
|
31
|
-
it("从 OpenClaw 注入的 api.pluginConfig 初始化运行期配置", () => {
|
|
32
|
-
assert.deepEqual(
|
|
33
|
-
extractApiPluginConfig({
|
|
34
|
-
pluginConfig: {
|
|
35
|
-
platformBaseUrl: "https://platform.example/api",
|
|
36
|
-
reportBaseUrl: "https://report.example/api",
|
|
37
|
-
authToken: "Bearer token",
|
|
38
|
-
recordUnattributed: true,
|
|
39
|
-
},
|
|
40
|
-
}),
|
|
41
|
-
{
|
|
42
|
-
platformBaseUrl: "https://platform.example/api",
|
|
43
|
-
reportBaseUrl: "https://report.example/api",
|
|
44
|
-
authToken: "Bearer token",
|
|
45
|
-
recordUnattributed: true,
|
|
46
|
-
}
|
|
47
|
-
);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("未配置时返回空对象", () => {
|
|
51
|
-
assert.deepEqual(extractApiPluginConfig({}), {});
|
|
52
|
-
});
|
|
53
|
-
});
|
|
1
|
+
import { describe, it, before } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
|
|
4
|
+
let isSkillMdReadPath: (filePath: string) => boolean;
|
|
5
|
+
let extractApiPluginConfig: (api: { pluginConfig?: Record<string, unknown> }) => Record<string, unknown>;
|
|
6
|
+
|
|
7
|
+
describe("isSkillMdReadPath", () => {
|
|
8
|
+
before(async () => {
|
|
9
|
+
({ isSkillMdReadPath, extractApiPluginConfig } = await import("./index.ts"));
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("POSIX 路径 basename 为 SKILL.md 时为 true", () => {
|
|
13
|
+
assert.equal(isSkillMdReadPath("/x/y/my-skill/SKILL.md"), true);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it(
|
|
17
|
+
"Windows 风格路径",
|
|
18
|
+
{ skip: process.platform !== "win32" },
|
|
19
|
+
() => {
|
|
20
|
+
assert.equal(isSkillMdReadPath(String.raw`C:\x\y\my-skill\SKILL.md`), true);
|
|
21
|
+
}
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
it("fooSKILL.md / my-SKILL.md 不误判", () => {
|
|
25
|
+
assert.equal(isSkillMdReadPath("/tmp/fooSKILL.md"), false);
|
|
26
|
+
assert.equal(isSkillMdReadPath("/tmp/my-SKILL.md"), false);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe("extractApiPluginConfig", () => {
|
|
31
|
+
it("从 OpenClaw 注入的 api.pluginConfig 初始化运行期配置", () => {
|
|
32
|
+
assert.deepEqual(
|
|
33
|
+
extractApiPluginConfig({
|
|
34
|
+
pluginConfig: {
|
|
35
|
+
platformBaseUrl: "https://platform.example/api",
|
|
36
|
+
reportBaseUrl: "https://report.example/api",
|
|
37
|
+
authToken: "Bearer token",
|
|
38
|
+
recordUnattributed: true,
|
|
39
|
+
},
|
|
40
|
+
}),
|
|
41
|
+
{
|
|
42
|
+
platformBaseUrl: "https://platform.example/api",
|
|
43
|
+
reportBaseUrl: "https://report.example/api",
|
|
44
|
+
authToken: "Bearer token",
|
|
45
|
+
recordUnattributed: true,
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("未配置时返回空对象", () => {
|
|
51
|
+
assert.deepEqual(extractApiPluginConfig({}), {});
|
|
52
|
+
});
|
|
53
|
+
});
|