@souls_market/openclaw-plugin 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.
@@ -0,0 +1,23 @@
1
+ import * as openclaw_plugin_sdk_core from 'openclaw/plugin-sdk/core';
2
+ import { OpenClawPluginApi } from 'openclaw/plugin-sdk/core';
3
+ import * as openclaw_plugin_sdk from 'openclaw/plugin-sdk';
4
+
5
+ type PluginDefinition = {
6
+ id?: string;
7
+ name?: string;
8
+ description?: string;
9
+ version?: string;
10
+ register?: (api: OpenClawPluginApi) => void | Promise<void>;
11
+ activate?: (api: OpenClawPluginApi) => void | Promise<void>;
12
+ };
13
+ declare const pluginDefinition: PluginDefinition;
14
+ declare function createPluginDefinition(): PluginDefinition;
15
+ declare const pluginEntry: {
16
+ id: string;
17
+ name: string;
18
+ description: string;
19
+ configSchema: openclaw_plugin_sdk.OpenClawPluginConfigSchema;
20
+ register: NonNullable<openclaw_plugin_sdk_core.OpenClawPluginDefinition["register"]>;
21
+ } & Pick<openclaw_plugin_sdk_core.OpenClawPluginDefinition, "kind">;
22
+
23
+ export { type PluginDefinition, createPluginDefinition, pluginEntry as default, pluginDefinition };
package/dist/index.js ADDED
@@ -0,0 +1,880 @@
1
+ // src/index.ts
2
+ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
3
+
4
+ // src/http.ts
5
+ var ApiRequestError = class extends Error {
6
+ url;
7
+ status;
8
+ responseBody;
9
+ constructor(message, url, status = 0, responseBody = null) {
10
+ super(message);
11
+ this.name = "ApiRequestError";
12
+ this.url = url;
13
+ this.status = status;
14
+ this.responseBody = responseBody;
15
+ }
16
+ };
17
+ function isPlainObject(value) {
18
+ return Boolean(value) && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype;
19
+ }
20
+ function resolveStringValue(value) {
21
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
22
+ }
23
+ function resolvePluginConfig(pluginConfig) {
24
+ return {
25
+ registryUrl: resolveStringValue(pluginConfig.registryUrl) ?? "https://api.souls.market",
26
+ apiToken: resolveStringValue(pluginConfig.apiToken)
27
+ };
28
+ }
29
+ function normalizeBaseUrl(registryUrl) {
30
+ const trimmed = registryUrl.trim();
31
+ return trimmed.endsWith("/") ? trimmed : `${trimmed}/`;
32
+ }
33
+ function createApiUrl(config, path, params) {
34
+ const baseUrl = normalizeBaseUrl(config.registryUrl);
35
+ const url = new URL(path.startsWith("/") ? path.slice(1) : path, baseUrl);
36
+ if (params) {
37
+ for (const [key, value] of Object.entries(params)) {
38
+ if (value.length === 0) {
39
+ continue;
40
+ }
41
+ url.searchParams.set(key, value);
42
+ }
43
+ }
44
+ return url.toString();
45
+ }
46
+ async function readResponseBody(response) {
47
+ try {
48
+ return await response.text();
49
+ } catch {
50
+ return "";
51
+ }
52
+ }
53
+ async function fetchApi(url, options) {
54
+ const headers = new Headers(options.headers);
55
+ if (options.apiToken) {
56
+ headers.set("authorization", `Bearer ${options.apiToken}`);
57
+ }
58
+ let body;
59
+ if (options.body !== void 0) {
60
+ if (typeof options.body === "string" || options.body instanceof FormData || options.body instanceof URLSearchParams || options.body instanceof Blob) {
61
+ body = options.body;
62
+ } else {
63
+ headers.set("content-type", "application/json");
64
+ body = JSON.stringify(options.body);
65
+ }
66
+ }
67
+ const signal = AbortSignal.timeout(options.timeout);
68
+ try {
69
+ const response = await fetch(url, {
70
+ method: options.method ?? "GET",
71
+ headers,
72
+ body,
73
+ signal
74
+ });
75
+ if (!response.ok) {
76
+ throw new ApiRequestError(
77
+ `Request failed with status ${response.status}`,
78
+ url,
79
+ response.status,
80
+ await readResponseBody(response)
81
+ );
82
+ }
83
+ return response;
84
+ } catch (error) {
85
+ if (error instanceof ApiRequestError) {
86
+ throw error;
87
+ }
88
+ if (error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError")) {
89
+ throw new ApiRequestError("Request timed out", url, 408);
90
+ }
91
+ const message = error instanceof Error ? error.message : String(error);
92
+ throw new ApiRequestError(message || "Request failed", url, 0);
93
+ }
94
+ }
95
+ function formatApiStatusMessage(error, context) {
96
+ const details = error.responseBody?.trim() ?? "";
97
+ if (error.status === 429) {
98
+ return `${context}\u89E6\u53D1\u4E86\u901F\u7387\u9650\u5236\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002`;
99
+ }
100
+ if (error.status === 408) {
101
+ return `\u8BF7\u6C42\u8D85\u65F6\uFF1A${context}\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002`;
102
+ }
103
+ if (error.status > 0) {
104
+ const suffix = details ? ` \u670D\u52A1\u5668\u8FD4\u56DE\uFF1A${details}` : "";
105
+ return `${context}\u5931\u8D25\uFF08HTTP ${error.status}\uFF09\u3002\u8BF7\u68C0\u67E5 Registry \u5730\u5740\u662F\u5426\u6B63\u786E\u6216\u7A0D\u540E\u91CD\u8BD5\u3002${suffix}`;
106
+ }
107
+ return `\u8FDE\u63A5\u5931\u8D25\uFF1A${context}\u3002\u8BF7\u68C0\u67E5\u7F51\u7EDC\u8FDE\u63A5\u548C Registry \u5730\u5740\u662F\u5426\u6B63\u786E\u3002`;
108
+ }
109
+ function formatErrorMessage(error, context) {
110
+ if (error instanceof ApiRequestError) {
111
+ return formatApiStatusMessage(error, context);
112
+ }
113
+ if (error instanceof Error) {
114
+ if (error.name === "AbortError" || error.name === "TimeoutError") {
115
+ return `\u8BF7\u6C42\u8D85\u65F6\uFF1A${context}\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002`;
116
+ }
117
+ return `\u8FDE\u63A5\u5931\u8D25\uFF1A${context}\u3002${error.message}`;
118
+ }
119
+ if (typeof error === "string" && error.trim()) {
120
+ return `${context}\u5931\u8D25\uFF1A${error.trim()}`;
121
+ }
122
+ if (isPlainObject(error)) {
123
+ const message = error.message;
124
+ if (typeof message === "string" && message.trim()) {
125
+ return `${context}\u5931\u8D25\uFF1A${message.trim()}`;
126
+ }
127
+ }
128
+ return `\u8FDE\u63A5\u5931\u8D25\uFF1A${context}\u3002\u8BF7\u68C0\u67E5\u7F51\u7EDC\u8FDE\u63A5\u548C Registry \u5730\u5740\u662F\u5426\u6B63\u786E\u3002`;
129
+ }
130
+
131
+ // src/tools/analyze.ts
132
+ import { Type } from "@sinclair/typebox";
133
+ import { DEFAULT_PERSONALITY, SOUL_CATEGORIES } from "@souls-market/shared";
134
+ import {
135
+ extractDescription,
136
+ extractDisplayName,
137
+ inferCategory,
138
+ inferLanguages,
139
+ inferPersonality,
140
+ inferTags
141
+ } from "@souls-market/shared/infer";
142
+
143
+ // src/result.ts
144
+ function buildTextResult(text) {
145
+ return {
146
+ details: {},
147
+ content: [
148
+ {
149
+ type: "text",
150
+ text
151
+ }
152
+ ]
153
+ };
154
+ }
155
+ function toolResult(text) {
156
+ return buildTextResult(text);
157
+ }
158
+ function toolJsonResult(data) {
159
+ return buildTextResult(JSON.stringify(data, null, 2));
160
+ }
161
+ function toolError(message, suggestion) {
162
+ const parts = [`\u539F\u56E0\uFF1A${message}`];
163
+ if (suggestion?.trim()) {
164
+ parts.push(`\u5EFA\u8BAE\uFF1A${suggestion.trim()}`);
165
+ }
166
+ return buildTextResult(parts.join("\n"));
167
+ }
168
+
169
+ // src/tools/analyze.ts
170
+ var ANALYZE_TIMEOUT_MS = 15e3;
171
+ var ANALYZE_MIN_LENGTH = 20;
172
+ var ANALYZE_MAX_LENGTH = 5e4;
173
+ var TAG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
174
+ var VALID_TONE_VALUES = ["professional", "casual", "friendly", "serious", "playful", "empathetic"];
175
+ var VALID_FORMALITY_VALUES = ["high", "medium", "low"];
176
+ var VALID_HUMOR_VALUES = ["high", "medium", "low", "none"];
177
+ var VALID_VERBOSITY_VALUES = ["concise", "moderate", "detailed", "verbose"];
178
+ var AnalyzeParametersSchema = Type.Object(
179
+ {
180
+ soulContent: Type.String()
181
+ },
182
+ {
183
+ additionalProperties: false
184
+ }
185
+ );
186
+ function isRecord(value) {
187
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
188
+ }
189
+ function normalizeString(value) {
190
+ return typeof value === "string" ? value.trim() : "";
191
+ }
192
+ function normalizeValidCategory(value, soulContent) {
193
+ if (typeof value === "string" && SOUL_CATEGORIES.some((category) => category.id === value)) {
194
+ return value;
195
+ }
196
+ return inferCategory(soulContent);
197
+ }
198
+ function normalizeTags(value, soulContent) {
199
+ const collected = /* @__PURE__ */ new Set();
200
+ if (Array.isArray(value)) {
201
+ for (const entry of value) {
202
+ if (typeof entry !== "string") {
203
+ continue;
204
+ }
205
+ const tag = entry.trim();
206
+ if (!TAG_PATTERN.test(tag)) {
207
+ continue;
208
+ }
209
+ collected.add(tag);
210
+ if (collected.size >= 10) {
211
+ break;
212
+ }
213
+ }
214
+ }
215
+ if (collected.size === 0) {
216
+ for (const tag of inferTags(soulContent)) {
217
+ const normalized = tag.trim();
218
+ if (!TAG_PATTERN.test(normalized)) {
219
+ continue;
220
+ }
221
+ collected.add(normalized);
222
+ if (collected.size >= 10) {
223
+ break;
224
+ }
225
+ }
226
+ }
227
+ const category = inferCategory(soulContent);
228
+ if (collected.size === 0) {
229
+ collected.add(category);
230
+ }
231
+ return [...collected].slice(0, 10);
232
+ }
233
+ function normalizePersonality(value) {
234
+ const source = isRecord(value) ? value : {};
235
+ return {
236
+ tone: typeof source.tone === "string" && VALID_TONE_VALUES.includes(source.tone) ? source.tone : DEFAULT_PERSONALITY.tone,
237
+ formality: typeof source.formality === "string" && VALID_FORMALITY_VALUES.includes(source.formality) ? source.formality : DEFAULT_PERSONALITY.formality,
238
+ humor: typeof source.humor === "string" && VALID_HUMOR_VALUES.includes(source.humor) ? source.humor : DEFAULT_PERSONALITY.humor,
239
+ verbosity: typeof source.verbosity === "string" && VALID_VERBOSITY_VALUES.includes(source.verbosity) ? source.verbosity : DEFAULT_PERSONALITY.verbosity
240
+ };
241
+ }
242
+ function normalizeDescription(value, soulContent) {
243
+ const description = normalizeString(value);
244
+ return description ? description.slice(0, 512) : extractDescription(soulContent).slice(0, 512);
245
+ }
246
+ function normalizeDisplayName(value) {
247
+ return normalizeString(value).slice(0, 128);
248
+ }
249
+ function normalizeLanguages(value, soulContent) {
250
+ const collected = /* @__PURE__ */ new Set();
251
+ if (Array.isArray(value)) {
252
+ for (const entry of value) {
253
+ if (typeof entry !== "string") {
254
+ continue;
255
+ }
256
+ const language = entry.trim();
257
+ if (!language) {
258
+ continue;
259
+ }
260
+ try {
261
+ const canonical = Intl.getCanonicalLocales(language)[0];
262
+ if (canonical) {
263
+ collected.add(canonical);
264
+ }
265
+ } catch {
266
+ continue;
267
+ }
268
+ }
269
+ }
270
+ if (collected.size === 0) {
271
+ for (const language of inferLanguages(soulContent)) {
272
+ const normalized = language.trim();
273
+ if (!normalized) {
274
+ continue;
275
+ }
276
+ try {
277
+ const canonical = Intl.getCanonicalLocales(normalized)[0];
278
+ if (canonical) {
279
+ collected.add(canonical);
280
+ }
281
+ } catch {
282
+ continue;
283
+ }
284
+ }
285
+ }
286
+ return [...collected];
287
+ }
288
+ function normalizeConfidence(value, fallback) {
289
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
290
+ }
291
+ function buildKeywordResult(soulContent) {
292
+ const category = inferCategory(soulContent);
293
+ const tags = normalizeTags([], soulContent);
294
+ return {
295
+ category,
296
+ tags,
297
+ personality: inferPersonality(soulContent),
298
+ description: extractDescription(soulContent).slice(0, 512),
299
+ displayName: extractDisplayName(soulContent) ?? "",
300
+ languages: inferLanguages(soulContent),
301
+ method: "keyword",
302
+ confidence: 0.4
303
+ };
304
+ }
305
+ function buildAiResult(soulContent, payload) {
306
+ const analysis = isRecord(payload) ? payload : {};
307
+ return {
308
+ category: normalizeValidCategory(analysis.category, soulContent),
309
+ tags: normalizeTags(analysis.tags, soulContent),
310
+ personality: normalizePersonality(analysis.personality),
311
+ description: normalizeDescription(analysis.description, soulContent),
312
+ displayName: normalizeDisplayName(analysis.displayName),
313
+ languages: normalizeLanguages(analysis.languages, soulContent),
314
+ method: "ai",
315
+ confidence: normalizeConfidence(analysis.confidence, 0.8)
316
+ };
317
+ }
318
+ function getAnalyzeInput(first, second) {
319
+ if (isRecord(second) && typeof second.soulContent === "string") {
320
+ return second;
321
+ }
322
+ if (isRecord(first) && typeof first.soulContent === "string") {
323
+ return first;
324
+ }
325
+ return { soulContent: "" };
326
+ }
327
+ function createAnalyzeTool(config, logger) {
328
+ return {
329
+ name: "soul_analyze",
330
+ label: "Soul Analyze",
331
+ description: "Analyze a SOUL.md snippet and infer category, tags, personality, languages, and summary metadata.",
332
+ parameters: AnalyzeParametersSchema,
333
+ execute: async (...args) => {
334
+ const input = getAnalyzeInput(args[0], args[1]);
335
+ const soulContent = input.soulContent.trim();
336
+ if (soulContent.length < ANALYZE_MIN_LENGTH) {
337
+ return toolError(
338
+ `SOUL \u5185\u5BB9\u81F3\u5C11\u9700\u8981 ${ANALYZE_MIN_LENGTH} \u4E2A\u5B57\u7B26\u3002`,
339
+ "\u8BF7\u63D0\u4F9B\u66F4\u5B8C\u6574\u7684 SOUL.md \u5185\u5BB9\u540E\u91CD\u8BD5\u3002"
340
+ );
341
+ }
342
+ if (soulContent.length > ANALYZE_MAX_LENGTH) {
343
+ return toolError(
344
+ `SOUL \u5185\u5BB9\u4E0D\u80FD\u8D85\u8FC7 ${ANALYZE_MAX_LENGTH} \u4E2A\u5B57\u7B26\u3002`,
345
+ "\u8BF7\u622A\u53D6\u4E00\u6BB5\u8F83\u77ED\u7684 SOUL.md \u5185\u5BB9\u540E\u91CD\u8BD5\u3002"
346
+ );
347
+ }
348
+ const url = createApiUrl(config, "/api/v1/tools/analyze-soul");
349
+ try {
350
+ const response = await fetchApi(url, {
351
+ method: "POST",
352
+ body: { soulContent },
353
+ timeout: ANALYZE_TIMEOUT_MS,
354
+ apiToken: config.apiToken
355
+ });
356
+ const payload = await response.json();
357
+ logger.info("soul_analyze: completed with ai method");
358
+ return toolJsonResult(buildAiResult(soulContent, payload));
359
+ } catch (error) {
360
+ if (error instanceof ApiRequestError && error.status === 429) {
361
+ logger.warn("soul_analyze: rate limited by registry api");
362
+ return toolError("\u5206\u6790\u8BF7\u6C42\u5DF2\u89E6\u53D1\u901F\u7387\u9650\u5236\u3002", "\u8BF7\u7A0D\u540E\u518D\u8BD5\uFF0C\u6216\u5728\u914D\u7F6E\u4E2D\u4F7F\u7528\u53EF\u7528\u7684 API Token\u3002");
363
+ }
364
+ if (error instanceof ApiRequestError && (error.status === 0 || error.status === 408)) {
365
+ logger.warn(`soul_analyze: fallback to keyword method due to api error status=${error.status}`);
366
+ return toolJsonResult(buildKeywordResult(soulContent));
367
+ }
368
+ if (error instanceof ApiRequestError) {
369
+ logger.error(`soul_analyze failed with api status=${error.status}: ${error.message}`);
370
+ return toolError(
371
+ formatErrorMessage(error, "\u5206\u6790 SOUL \u5185\u5BB9"),
372
+ "\u8BF7\u7A0D\u540E\u91CD\u8BD5\uFF0C\u6216\u786E\u8BA4 Registry \u5730\u5740\u4E0E API Token \u914D\u7F6E\u6B63\u786E\u3002"
373
+ );
374
+ }
375
+ const message = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
376
+ logger.warn(`soul_analyze: fallback to keyword method due to unexpected error: ${message}`);
377
+ return toolJsonResult(buildKeywordResult(soulContent));
378
+ }
379
+ }
380
+ };
381
+ }
382
+
383
+ // src/tools/export.ts
384
+ import { access, mkdir, readdir, readFile, writeFile } from "fs/promises";
385
+ import { homedir } from "os";
386
+ import { basename, dirname, join, relative, resolve, sep } from "path";
387
+ import { Type as Type2 } from "@sinclair/typebox";
388
+ import { DEFAULT_PERSONALITY as DEFAULT_PERSONALITY2 } from "@souls-market/shared";
389
+ import {
390
+ extractDescription as extractDescription2,
391
+ extractDisplayName as extractDisplayName2,
392
+ inferCategory as inferCategory2,
393
+ inferLanguages as inferLanguages2,
394
+ inferPersonality as inferPersonality2,
395
+ inferTags as inferTags2
396
+ } from "@souls-market/shared/infer";
397
+ import { SECURITY_SCAN_RULES, scanSoulPackage } from "@souls-market/shared/scanner";
398
+ var DEFAULT_COMPATIBILITY = {
399
+ openclaw: ">=0.5.0",
400
+ models: ["anthropic/claude-*", "openai/gpt-4*"]
401
+ };
402
+ var DEFAULT_AUTHOR_NAME = "openclaw-agent";
403
+ var DEFAULT_EXPORT_DIR_NAME = "exported-soul";
404
+ var DEFAULT_LICENSE = "UNLICENSED";
405
+ var SOUL_FILE_NAMES = ["SOUL.md", "IDENTITY.md", "AGENTS.md", "TOOLS.md", "USER.md", "BOOTSTRAP.md"];
406
+ var VALID_AGENT_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
407
+ var REDACTED_TOKEN = "[REDACTED]";
408
+ var ExportParametersSchema = Type2.Object(
409
+ {
410
+ agentId: Type2.Optional(Type2.String()),
411
+ includeUserMd: Type2.Optional(Type2.Boolean())
412
+ },
413
+ {
414
+ additionalProperties: false
415
+ }
416
+ );
417
+ async function exists(targetPath) {
418
+ try {
419
+ await access(targetPath);
420
+ return true;
421
+ } catch {
422
+ return false;
423
+ }
424
+ }
425
+ function getHomeDir(deps) {
426
+ return deps.homeDir?.() ?? homedir();
427
+ }
428
+ function getOpenClawRoot(deps) {
429
+ return join(getHomeDir(deps), ".openclaw");
430
+ }
431
+ async function resolveCurrentWorkspaceDir(runtimeApi, deps) {
432
+ const fallback = resolve(getOpenClawRoot(deps), "workspace");
433
+ const candidates = [
434
+ runtimeApi.agent?.resolveAgentWorkspaceDir,
435
+ runtimeApi.resolveAgentWorkspaceDir,
436
+ runtimeApi.resolveCurrentWorkspaceDir
437
+ ];
438
+ for (const candidate of candidates) {
439
+ if (typeof candidate !== "function") {
440
+ continue;
441
+ }
442
+ try {
443
+ const resolvedPath = await candidate();
444
+ if (typeof resolvedPath === "string" && resolvedPath.trim()) {
445
+ return resolve(resolvedPath);
446
+ }
447
+ } catch {
448
+ continue;
449
+ }
450
+ }
451
+ return fallback;
452
+ }
453
+ function resolveAgentWorkspaceDir(agentId, deps) {
454
+ return resolve(getOpenClawRoot(deps), "agents", agentId, "workspace");
455
+ }
456
+ function isPathInside(parentDir, targetPath) {
457
+ const normalizedParent = resolve(parentDir);
458
+ const normalizedTarget = resolve(targetPath);
459
+ const relativePath = relative(normalizedParent, normalizedTarget);
460
+ return relativePath === "" || !relativePath.startsWith(`..${sep}`) && relativePath !== ".." && !relativePath.includes(`..${sep}`);
461
+ }
462
+ function ensureValidAgentId(agentId) {
463
+ if (!VALID_AGENT_ID_PATTERN.test(agentId)) {
464
+ throw new Error("agentId \u683C\u5F0F\u4E0D\u5408\u6CD5\uFF0C\u53EA\u5141\u8BB8\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF\u548C\u77ED\u6A2A\u7EBF\u3002");
465
+ }
466
+ return agentId;
467
+ }
468
+ function resolveExportDir(workspaceDir) {
469
+ return resolve(dirname(workspaceDir), DEFAULT_EXPORT_DIR_NAME);
470
+ }
471
+ function slugify(input) {
472
+ return input.trim().replace(/['"]/g, "").replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[^a-zA-Z0-9]+/g, "-").replace(/-{2,}/g, "-").replace(/^-|-$/g, "").toLowerCase();
473
+ }
474
+ function toTitleCase(input) {
475
+ return input.split(/[-_\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join(" ");
476
+ }
477
+ async function ensureEmptyOutputDir(outputDir) {
478
+ try {
479
+ const entries = await readdir(outputDir);
480
+ if (entries.length > 0) {
481
+ throw new Error(`\u5BFC\u51FA\u76EE\u5F55\u5DF2\u5B58\u5728\u4E14\u975E\u7A7A\uFF1A${outputDir}`);
482
+ }
483
+ } catch (error) {
484
+ if (error.code !== "ENOENT") {
485
+ throw error;
486
+ }
487
+ }
488
+ await mkdir(outputDir, { recursive: true });
489
+ }
490
+ async function readWorkspaceFiles(workspaceDir) {
491
+ const files = {};
492
+ for (const fileName of SOUL_FILE_NAMES) {
493
+ try {
494
+ files[fileName] = await readFile(join(workspaceDir, fileName), "utf8");
495
+ } catch (error) {
496
+ if (error.code === "ENOENT") {
497
+ files[fileName] = null;
498
+ continue;
499
+ }
500
+ throw error;
501
+ }
502
+ }
503
+ return files;
504
+ }
505
+ function getRulePattern(rule) {
506
+ const flags = rule.pattern.flags.includes("g") ? rule.pattern.flags : `${rule.pattern.flags}g`;
507
+ return new RegExp(rule.pattern.source, flags);
508
+ }
509
+ function countLineNumber(content, index) {
510
+ return content.slice(0, index).split(/\r?\n/).length;
511
+ }
512
+ function buildSecretWarnings(fileName, content) {
513
+ const warnings = [];
514
+ for (const rule of SECURITY_SCAN_RULES) {
515
+ if (rule.category !== "secrets-detection") {
516
+ continue;
517
+ }
518
+ const matcher = getRulePattern(rule);
519
+ for (const match of content.matchAll(matcher)) {
520
+ const matchIndex = match.index ?? 0;
521
+ warnings.push({
522
+ level: rule.severity,
523
+ rule: rule.id,
524
+ message: `${rule.message}\uFF08\u5DF2\u5728\u5BFC\u51FA\u5185\u5BB9\u4E2D\u8131\u654F\uFF09`,
525
+ path: fileName,
526
+ line: countLineNumber(content, matchIndex),
527
+ match: REDACTED_TOKEN,
528
+ category: rule.category
529
+ });
530
+ }
531
+ }
532
+ return warnings;
533
+ }
534
+ function redactSecrets(content) {
535
+ let sanitized = content;
536
+ for (const rule of SECURITY_SCAN_RULES) {
537
+ if (rule.category !== "secrets-detection") {
538
+ continue;
539
+ }
540
+ sanitized = sanitized.replace(getRulePattern(rule), REDACTED_TOKEN);
541
+ }
542
+ return sanitized;
543
+ }
544
+ function sanitizeFileContent(fileName, content) {
545
+ const securityWarnings = buildSecretWarnings(fileName, content);
546
+ const sanitizedContent = redactSecrets(content);
547
+ return {
548
+ content: sanitizedContent,
549
+ securityWarnings,
550
+ redacted: securityWarnings.length > 0
551
+ };
552
+ }
553
+ function buildSoulJson(params) {
554
+ const displayName = extractDisplayName2(params.identityContent ?? "")?.trim();
555
+ const name = slugify(displayName || params.agentId || basename(dirname(params.workspaceDir)) || "exported-soul") || "exported-soul";
556
+ return {
557
+ name,
558
+ displayName: displayName || toTitleCase(name),
559
+ version: "0.1.0",
560
+ description: extractDescription2(params.soulContent),
561
+ author: {
562
+ name: params.agentId || DEFAULT_AUTHOR_NAME
563
+ },
564
+ license: DEFAULT_LICENSE,
565
+ tags: inferTags2(params.soulContent),
566
+ category: inferCategory2(params.soulContent),
567
+ compatibility: {
568
+ openclaw: DEFAULT_COMPATIBILITY.openclaw,
569
+ models: [...DEFAULT_COMPATIBILITY.models]
570
+ },
571
+ languages: inferLanguages2(params.soulContent),
572
+ personality: inferPersonality2(params.soulContent) ?? DEFAULT_PERSONALITY2
573
+ };
574
+ }
575
+ async function writeExportFile(outputDir, fileName, content) {
576
+ await writeFile(join(outputDir, fileName), content, "utf8");
577
+ }
578
+ function mapSecurityWarnings(scanResult) {
579
+ return scanResult.diagnostics.map((diagnostic) => ({
580
+ level: diagnostic.level,
581
+ rule: diagnostic.rule,
582
+ message: diagnostic.message,
583
+ path: diagnostic.path,
584
+ line: diagnostic.line,
585
+ match: diagnostic.category === "secrets-detection" && diagnostic.match ? REDACTED_TOKEN : diagnostic.match,
586
+ category: diagnostic.category
587
+ }));
588
+ }
589
+ function getExportInput(first, second) {
590
+ const isRecord3 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
591
+ if (isRecord3(second)) {
592
+ return second;
593
+ }
594
+ if (isRecord3(first)) {
595
+ return first;
596
+ }
597
+ return {};
598
+ }
599
+ function formatExportError(error) {
600
+ if (error instanceof Error) {
601
+ const code = error.code;
602
+ if (code === "EACCES" || code === "EPERM") {
603
+ return "\u5BFC\u51FA\u5931\u8D25\uFF1A\u6587\u4EF6\u7CFB\u7EDF\u6743\u9650\u4E0D\u8DB3\u3002";
604
+ }
605
+ if (code === "ENOSPC") {
606
+ return "\u5BFC\u51FA\u5931\u8D25\uFF1A\u78C1\u76D8\u7A7A\u95F4\u4E0D\u8DB3\u3002";
607
+ }
608
+ if (code === "ENOENT") {
609
+ return "\u5BFC\u51FA\u5931\u8D25\uFF1A\u5DE5\u4F5C\u533A\u6587\u4EF6\u5728\u5BFC\u51FA\u8FC7\u7A0B\u4E2D\u4E0D\u5B58\u5728\u6216\u5DF2\u88AB\u79FB\u9664\u3002";
610
+ }
611
+ return `\u5BFC\u51FA\u5931\u8D25\uFF1A${error.message}`;
612
+ }
613
+ return `\u5BFC\u51FA\u5931\u8D25\uFF1A${String(error)}`;
614
+ }
615
+ function createExportTool(_config, runtimeApi = {}, logger, deps = {}) {
616
+ return {
617
+ name: "soul_export",
618
+ label: "Soul Export",
619
+ description: "Export the current OpenClaw agent workspace as a SOUL package.",
620
+ parameters: ExportParametersSchema,
621
+ execute: async (...args) => {
622
+ try {
623
+ const input = getExportInput(args[0], args[1]);
624
+ const rawAgentId = input.agentId?.trim();
625
+ const agentId = rawAgentId ? ensureValidAgentId(rawAgentId) : void 0;
626
+ const includeUserMd = input.includeUserMd === true;
627
+ const workspaceDir = agentId ? resolveAgentWorkspaceDir(agentId, deps) : await resolveCurrentWorkspaceDir(runtimeApi, deps);
628
+ const openClawRoot = getOpenClawRoot(deps);
629
+ if (agentId && !isPathInside(openClawRoot, workspaceDir)) {
630
+ logger.warn(`soul_export rejected agentId due to unsafe path: ${agentId}`);
631
+ return toolError("agentId \u89E3\u6790\u51FA\u7684\u5DE5\u4F5C\u533A\u8DEF\u5F84\u8D85\u51FA\u4E86\u5141\u8BB8\u8303\u56F4\u3002", "\u8BF7\u68C0\u67E5 agentId \u662F\u5426\u6B63\u786E\u3002");
632
+ }
633
+ if (!await exists(join(workspaceDir, "SOUL.md"))) {
634
+ logger.warn(`soul_export missing SOUL.md in workspace: ${workspaceDir}`);
635
+ return toolError("\u5F53\u524D\u5DE5\u4F5C\u533A\u672A\u627E\u5230 SOUL \u914D\u7F6E\u6587\u4EF6", "\u8BF7\u786E\u8BA4 agentId \u662F\u5426\u6B63\u786E\uFF0C\u6216\u5148\u5728\u5DE5\u4F5C\u533A\u4E2D\u521B\u5EFA SOUL.md\u3002");
636
+ }
637
+ const outputDir = resolveExportDir(workspaceDir);
638
+ await ensureEmptyOutputDir(outputDir);
639
+ const workspaceFiles = await readWorkspaceFiles(workspaceDir);
640
+ const exportedFiles = [];
641
+ const excludedFiles = [];
642
+ const preScanWarnings = [];
643
+ const sanitizedWorkspaceFiles = { ...workspaceFiles };
644
+ for (const fileName of SOUL_FILE_NAMES) {
645
+ if (fileName === "USER.md" && !includeUserMd && workspaceFiles[fileName] !== null) {
646
+ excludedFiles.push({
647
+ fileName,
648
+ reason: "\u9ED8\u8BA4\u6392\u9664 USER.md \u4EE5\u4FDD\u62A4\u7528\u6237\u9690\u79C1"
649
+ });
650
+ continue;
651
+ }
652
+ const content = workspaceFiles[fileName];
653
+ if (content === null) {
654
+ continue;
655
+ }
656
+ const sanitizedFile = sanitizeFileContent(fileName, content);
657
+ sanitizedWorkspaceFiles[fileName] = sanitizedFile.content;
658
+ preScanWarnings.push(...sanitizedFile.securityWarnings);
659
+ await writeExportFile(outputDir, fileName, sanitizedFile.content);
660
+ exportedFiles.push({ fileName, source: "workspace" });
661
+ }
662
+ const soulJson = buildSoulJson({
663
+ agentId,
664
+ workspaceDir,
665
+ soulContent: sanitizedWorkspaceFiles["SOUL.md"] ?? "",
666
+ identityContent: sanitizedWorkspaceFiles["IDENTITY.md"]
667
+ });
668
+ const soulJsonText = `${JSON.stringify(soulJson, null, 2)}
669
+ `;
670
+ const sanitizedSoulJson = sanitizeFileContent("soul.json", soulJsonText);
671
+ preScanWarnings.push(...sanitizedSoulJson.securityWarnings);
672
+ await writeExportFile(outputDir, "soul.json", sanitizedSoulJson.content);
673
+ exportedFiles.push({ fileName: "soul.json", source: "generated" });
674
+ const exportedSoulJson = JSON.parse(sanitizedSoulJson.content);
675
+ const securityWarnings = [...preScanWarnings];
676
+ let securityScanPassed = preScanWarnings.every((warning) => warning.level !== "error");
677
+ if (preScanWarnings.length > 0) {
678
+ logger.warn(`soul_export detected ${preScanWarnings.length} pre-scan warning(s)`);
679
+ }
680
+ try {
681
+ const scanResult = await scanSoulPackage(outputDir);
682
+ securityWarnings.push(...mapSecurityWarnings(scanResult));
683
+ securityScanPassed = securityScanPassed && scanResult.passed;
684
+ if (!scanResult.passed || scanResult.diagnostics.length > 0) {
685
+ logger.warn(`soul_export scan diagnostics: ${scanResult.diagnostics.length}`);
686
+ }
687
+ } catch (error) {
688
+ securityScanPassed = false;
689
+ const message = error instanceof Error ? error.message : String(error);
690
+ logger.error(`soul_export scan failed: ${message}`);
691
+ securityWarnings.push({
692
+ level: "warn",
693
+ rule: "security-scan-failed",
694
+ message: `\u5B89\u5168\u626B\u63CF\u5931\u8D25\uFF1A${message}`
695
+ });
696
+ }
697
+ const payload = {
698
+ workspaceDir,
699
+ outputDir,
700
+ files: exportedFiles,
701
+ excluded: excludedFiles,
702
+ securityWarnings,
703
+ securityScanPassed,
704
+ soulJson: exportedSoulJson
705
+ };
706
+ logger.info(
707
+ `soul_export completed: workspace=${workspaceDir}, output=${outputDir}, files=${exportedFiles.length}, excluded=${excludedFiles.length}, warnings=${securityWarnings.length}`
708
+ );
709
+ return toolJsonResult(payload);
710
+ } catch (error) {
711
+ const message = error instanceof Error ? error.message : String(error);
712
+ logger.error(`soul_export failed: ${message}`);
713
+ return toolError(formatExportError(error), "\u8BF7\u68C0\u67E5\u5DE5\u4F5C\u533A\u8DEF\u5F84\u3001\u5BFC\u51FA\u76EE\u5F55\u6743\u9650\u6216\u78C1\u76D8\u7A7A\u95F4\u540E\u91CD\u8BD5\u3002");
714
+ }
715
+ }
716
+ };
717
+ }
718
+
719
+ // src/tools/search.ts
720
+ import { Type as Type3 } from "@sinclair/typebox";
721
+ var SEARCH_TIMEOUT_MS = 1e4;
722
+ var SearchParametersSchema = Type3.Object(
723
+ {
724
+ query: Type3.String(),
725
+ category: Type3.Optional(Type3.String())
726
+ },
727
+ {
728
+ additionalProperties: false
729
+ }
730
+ );
731
+ function isRecord2(value) {
732
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
733
+ }
734
+ function normalizeString2(value) {
735
+ return typeof value === "string" ? value.trim() : "";
736
+ }
737
+ function normalizeStringArray(value) {
738
+ if (!Array.isArray(value)) {
739
+ return [];
740
+ }
741
+ return value.map((item) => normalizeString2(item)).filter((item) => item.length > 0);
742
+ }
743
+ function normalizeNumber(value) {
744
+ if (typeof value === "number" && Number.isFinite(value)) {
745
+ return value;
746
+ }
747
+ if (typeof value === "string" && value.trim()) {
748
+ const parsed = Number(value);
749
+ return Number.isFinite(parsed) ? parsed : null;
750
+ }
751
+ return null;
752
+ }
753
+ function normalizeSearchItem(item) {
754
+ const ratingSource = item.ratingAvg ?? item.rating;
755
+ return {
756
+ name: normalizeString2(item.name),
757
+ displayName: normalizeString2(item.displayName),
758
+ description: normalizeString2(item.description),
759
+ category: normalizeString2(item.category),
760
+ tags: normalizeStringArray(item.tags),
761
+ rating: normalizeNumber(ratingSource),
762
+ downloads: normalizeNumber(item.downloads) ?? 0
763
+ };
764
+ }
765
+ function normalizeSearchResponse(payload) {
766
+ const response = isRecord2(payload) ? payload : {};
767
+ const items = Array.isArray(response.data) ? response.data : [];
768
+ const pagination = {
769
+ page: normalizeNumber(response.pagination?.page) ?? 1,
770
+ perPage: normalizeNumber(response.pagination?.perPage) ?? 20,
771
+ total: normalizeNumber(response.pagination?.total) ?? normalizeNumber(response.total) ?? 0,
772
+ totalPages: normalizeNumber(response.pagination?.totalPages) ?? 0
773
+ };
774
+ return {
775
+ results: items.filter(isRecord2).map((item) => normalizeSearchItem(item)),
776
+ total: pagination.total,
777
+ pagination,
778
+ query: normalizeString2(response.query)
779
+ };
780
+ }
781
+ function getSearchInput(first, second) {
782
+ if (isRecord2(second) && typeof second.query === "string") {
783
+ return second;
784
+ }
785
+ if (isRecord2(first) && typeof first.query === "string") {
786
+ return first;
787
+ }
788
+ return { query: "" };
789
+ }
790
+ function stringifyError(error) {
791
+ if (error instanceof Error) {
792
+ return `${error.name}: ${error.message}`;
793
+ }
794
+ return String(error);
795
+ }
796
+ function createSearchTool(config, logger) {
797
+ return {
798
+ name: "soul_search",
799
+ label: "Soul Search",
800
+ description: "Search Soul Market for matching SOUL templates by keyword and category.",
801
+ parameters: SearchParametersSchema,
802
+ execute: async (...args) => {
803
+ const input = getSearchInput(args[0], args[1]);
804
+ const query = input.query.trim();
805
+ const category = typeof input.category === "string" ? input.category.trim() : "";
806
+ if (!query) {
807
+ return toolError("\u641C\u7D22\u8BCD\u4E0D\u80FD\u4E3A\u7A7A\u3002", "\u8BF7\u8F93\u5165\u4E00\u4E2A\u4E0D\u8D85\u8FC7 200 \u4E2A\u5B57\u7B26\u7684\u67E5\u8BE2\u8BCD\u3002");
808
+ }
809
+ if (query.length > 200) {
810
+ return toolError("\u641C\u7D22\u8BCD\u957F\u5EA6\u4E0D\u80FD\u8D85\u8FC7 200 \u4E2A\u5B57\u7B26\u3002", "\u8BF7\u7F29\u77ED\u67E5\u8BE2\u8BCD\u540E\u91CD\u8BD5\u3002");
811
+ }
812
+ try {
813
+ const url = createApiUrl(
814
+ config,
815
+ "/api/v1/search",
816
+ {
817
+ q: query,
818
+ ...category ? { category } : {}
819
+ }
820
+ );
821
+ const response = await fetchApi(url, {
822
+ method: "GET",
823
+ timeout: SEARCH_TIMEOUT_MS
824
+ });
825
+ const payload = await response.json();
826
+ const normalized = normalizeSearchResponse(payload);
827
+ if (normalized.results.length === 0) {
828
+ logger.info(`soul_search: no results for query="${query}" category="${category || "all"}"`);
829
+ return toolResult("\u672A\u627E\u5230\u5339\u914D\u7684 SOUL \u6A21\u677F");
830
+ }
831
+ logger.info(`soul_search: found ${normalized.results.length} result(s) for query="${query}"`);
832
+ return toolJsonResult(normalized);
833
+ } catch (error) {
834
+ logger.warn(`soul_search request failed: ${stringifyError(error)}`);
835
+ if (error instanceof ApiRequestError && error.status === 429) {
836
+ return toolError("\u641C\u7D22\u8BF7\u6C42\u8FC7\u4E8E\u9891\u7E41\uFF0C\u5DF2\u89E6\u53D1\u901F\u7387\u9650\u5236\u3002", "\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002");
837
+ }
838
+ return toolError(
839
+ formatErrorMessage(error, "\u641C\u7D22 SOUL \u6A21\u677F"),
840
+ "\u8BF7\u786E\u8BA4 Registry \u5730\u5740\u914D\u7F6E\u6B63\u786E\u5E76\u68C0\u67E5\u7F51\u7EDC\u8FDE\u63A5\u3002"
841
+ );
842
+ }
843
+ }
844
+ };
845
+ }
846
+
847
+ // src/index.ts
848
+ function registerTools(api) {
849
+ const config = resolvePluginConfig(api.pluginConfig ?? {});
850
+ api.registerTool(createSearchTool(config, api.logger));
851
+ api.registerTool(createExportTool(config, api.runtime, api.logger));
852
+ api.registerTool(createAnalyzeTool(config, api.logger));
853
+ }
854
+ var pluginDefinition = {
855
+ id: "soul-market",
856
+ name: "Soul Market",
857
+ description: "Search, export, and analyze SOUL packages from OpenClaw.",
858
+ version: "0.0.1",
859
+ register(api) {
860
+ registerTools(api);
861
+ }
862
+ };
863
+ function createPluginDefinition() {
864
+ return pluginDefinition;
865
+ }
866
+ var pluginEntry = definePluginEntry({
867
+ id: "soul-market",
868
+ name: "Soul Market",
869
+ description: "Search, export, and analyze SOUL packages from OpenClaw.",
870
+ register(api) {
871
+ registerTools(api);
872
+ }
873
+ });
874
+ var index_default = pluginEntry;
875
+ export {
876
+ createPluginDefinition,
877
+ index_default as default,
878
+ pluginDefinition
879
+ };
880
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/http.ts","../src/tools/analyze.ts","../src/result.ts","../src/tools/export.ts","../src/tools/search.ts"],"sourcesContent":["import type { OpenClawPluginApi } from 'openclaw/plugin-sdk/core';\nimport { definePluginEntry } from 'openclaw/plugin-sdk/plugin-entry';\n\nimport { resolvePluginConfig } from './http';\nimport { createAnalyzeTool } from './tools/analyze';\nimport { createExportTool, type PluginRuntimeApi } from './tools/export';\nimport { createSearchTool } from './tools/search';\n\nexport type PluginDefinition = {\n id?: string;\n name?: string;\n description?: string;\n version?: string;\n register?: (api: OpenClawPluginApi) => void | Promise<void>;\n activate?: (api: OpenClawPluginApi) => void | Promise<void>;\n};\n\nfunction registerTools(api: OpenClawPluginApi): void {\n const config = resolvePluginConfig(api.pluginConfig ?? {});\n api.registerTool(createSearchTool(config, api.logger));\n api.registerTool(createExportTool(config, api.runtime as unknown as PluginRuntimeApi, api.logger));\n api.registerTool(createAnalyzeTool(config, api.logger));\n}\n\nexport const pluginDefinition: PluginDefinition = {\n id: 'soul-market',\n name: 'Soul Market',\n description: 'Search, export, and analyze SOUL packages from OpenClaw.',\n version: '0.0.1',\n register(api: OpenClawPluginApi): void {\n registerTools(api);\n }\n};\n\nexport function createPluginDefinition(): PluginDefinition {\n return pluginDefinition;\n}\n\nconst pluginEntry = definePluginEntry({\n id: 'soul-market',\n name: 'Soul Market',\n description: 'Search, export, and analyze SOUL packages from OpenClaw.',\n register(api: OpenClawPluginApi): void {\n registerTools(api);\n }\n});\n\nexport default pluginEntry;\n","export interface PluginConfig {\n registryUrl: string;\n apiToken?: string;\n}\n\nexport interface FetchApiOptions {\n method?: string;\n body?: unknown;\n timeout: number;\n apiToken?: string;\n headers?: HeadersInit;\n}\n\nexport class ApiRequestError extends Error {\n public readonly url: string;\n public readonly status: number;\n public readonly responseBody: string | null;\n\n constructor(message: string, url: string, status = 0, responseBody: string | null = null) {\n super(message);\n this.name = 'ApiRequestError';\n this.url = url;\n this.status = status;\n this.responseBody = responseBody;\n }\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === 'object' && Object.getPrototypeOf(value) === Object.prototype;\n}\n\nfunction resolveStringValue(value: unknown): string | undefined {\n return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;\n}\n\nexport function resolvePluginConfig(pluginConfig: Record<string, unknown>): PluginConfig {\n return {\n registryUrl: resolveStringValue(pluginConfig.registryUrl) ?? 'https://api.souls.market',\n apiToken: resolveStringValue(pluginConfig.apiToken)\n };\n}\n\nfunction normalizeBaseUrl(registryUrl: string): string {\n const trimmed = registryUrl.trim();\n return trimmed.endsWith('/') ? trimmed : `${trimmed}/`;\n}\n\nexport function createApiUrl(\n config: Pick<PluginConfig, 'registryUrl'>,\n path: string,\n params?: Record<string, string>\n): string {\n const baseUrl = normalizeBaseUrl(config.registryUrl);\n const url = new URL(path.startsWith('/') ? path.slice(1) : path, baseUrl);\n\n if (params) {\n for (const [key, value] of Object.entries(params)) {\n if (value.length === 0) {\n continue;\n }\n\n url.searchParams.set(key, value);\n }\n }\n\n return url.toString();\n}\n\nasync function readResponseBody(response: Response): Promise<string> {\n try {\n return await response.text();\n } catch {\n return '';\n }\n}\n\nexport async function fetchApi(url: string, options: FetchApiOptions): Promise<Response> {\n const headers = new Headers(options.headers);\n if (options.apiToken) {\n headers.set('authorization', `Bearer ${options.apiToken}`);\n }\n\n let body: BodyInit | undefined;\n if (options.body !== undefined) {\n if (typeof options.body === 'string' || options.body instanceof FormData || options.body instanceof URLSearchParams || options.body instanceof Blob) {\n body = options.body;\n } else {\n headers.set('content-type', 'application/json');\n body = JSON.stringify(options.body);\n }\n }\n\n const signal = AbortSignal.timeout(options.timeout);\n\n try {\n const response = await fetch(url, {\n method: options.method ?? 'GET',\n headers,\n body,\n signal\n });\n\n if (!response.ok) {\n throw new ApiRequestError(\n `Request failed with status ${response.status}`,\n url,\n response.status,\n await readResponseBody(response)\n );\n }\n\n return response;\n } catch (error) {\n if (error instanceof ApiRequestError) {\n throw error;\n }\n\n if (error instanceof Error && (error.name === 'AbortError' || error.name === 'TimeoutError')) {\n throw new ApiRequestError('Request timed out', url, 408);\n }\n\n const message = error instanceof Error ? error.message : String(error);\n throw new ApiRequestError(message || 'Request failed', url, 0);\n }\n}\n\nfunction formatApiStatusMessage(error: ApiRequestError, context: string): string {\n const details = error.responseBody?.trim() ?? '';\n\n if (error.status === 429) {\n return `${context}触发了速率限制,请稍后重试。`;\n }\n\n if (error.status === 408) {\n return `请求超时:${context},请稍后重试。`;\n }\n\n if (error.status > 0) {\n const suffix = details ? ` 服务器返回:${details}` : '';\n return `${context}失败(HTTP ${error.status})。请检查 Registry 地址是否正确或稍后重试。${suffix}`;\n }\n\n return `连接失败:${context}。请检查网络连接和 Registry 地址是否正确。`;\n}\n\nexport function formatErrorMessage(error: unknown, context: string): string {\n if (error instanceof ApiRequestError) {\n return formatApiStatusMessage(error, context);\n }\n\n if (error instanceof Error) {\n if (error.name === 'AbortError' || error.name === 'TimeoutError') {\n return `请求超时:${context},请稍后重试。`;\n }\n\n return `连接失败:${context}。${error.message}`;\n }\n\n if (typeof error === 'string' && error.trim()) {\n return `${context}失败:${error.trim()}`;\n }\n\n if (isPlainObject(error)) {\n const message = error.message;\n if (typeof message === 'string' && message.trim()) {\n return `${context}失败:${message.trim()}`;\n }\n }\n\n return `连接失败:${context}。请检查网络连接和 Registry 地址是否正确。`;\n}\n","import { Type, type Static } from '@sinclair/typebox';\nimport type { PluginLogger } from 'openclaw/plugin-sdk';\n\nimport { DEFAULT_PERSONALITY, SOUL_CATEGORIES, type SoulPersonality } from '@souls-market/shared';\nimport {\n extractDescription,\n extractDisplayName,\n inferCategory,\n inferLanguages,\n inferPersonality,\n inferTags\n} from '@souls-market/shared/infer';\n\nimport { ApiRequestError, createApiUrl, fetchApi, formatErrorMessage, type PluginConfig } from '../http';\nimport { toolError, toolJsonResult, type ToolResult } from '../result';\n\nconst ANALYZE_TIMEOUT_MS = 15_000;\nconst ANALYZE_MIN_LENGTH = 20;\nconst ANALYZE_MAX_LENGTH = 50_000;\nconst TAG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\nconst VALID_TONE_VALUES: SoulPersonality['tone'][] = ['professional', 'casual', 'friendly', 'serious', 'playful', 'empathetic'];\nconst VALID_FORMALITY_VALUES: SoulPersonality['formality'][] = ['high', 'medium', 'low'];\nconst VALID_HUMOR_VALUES: SoulPersonality['humor'][] = ['high', 'medium', 'low', 'none'];\nconst VALID_VERBOSITY_VALUES: SoulPersonality['verbosity'][] = ['concise', 'moderate', 'detailed', 'verbose'];\n\nconst AnalyzeParametersSchema = Type.Object(\n {\n soulContent: Type.String()\n },\n {\n additionalProperties: false\n }\n);\n\nexport type AnalyzeToolInput = Static<typeof AnalyzeParametersSchema>;\n\nexport interface AnalyzeToolDefinition {\n name: 'soul_analyze';\n label: string;\n description: string;\n parameters: typeof AnalyzeParametersSchema;\n execute: (...args: [unknown, unknown?]) => Promise<ToolResult>;\n}\n\ninterface AnalyzeApiPayload {\n category?: unknown;\n tags?: unknown;\n personality?: unknown;\n description?: unknown;\n displayName?: unknown;\n languages?: unknown;\n confidence?: unknown;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction normalizeString(value: unknown): string {\n return typeof value === 'string' ? value.trim() : '';\n}\n\nfunction normalizeValidCategory(value: unknown, soulContent: string): string {\n if (typeof value === 'string' && SOUL_CATEGORIES.some((category) => category.id === value)) {\n return value;\n }\n\n return inferCategory(soulContent);\n}\n\nfunction normalizeTags(value: unknown, soulContent: string): string[] {\n const collected = new Set<string>();\n\n if (Array.isArray(value)) {\n for (const entry of value) {\n if (typeof entry !== 'string') {\n continue;\n }\n\n const tag = entry.trim();\n if (!TAG_PATTERN.test(tag)) {\n continue;\n }\n\n collected.add(tag);\n if (collected.size >= 10) {\n break;\n }\n }\n }\n\n if (collected.size === 0) {\n for (const tag of inferTags(soulContent)) {\n const normalized = tag.trim();\n if (!TAG_PATTERN.test(normalized)) {\n continue;\n }\n\n collected.add(normalized);\n if (collected.size >= 10) {\n break;\n }\n }\n }\n\n const category = inferCategory(soulContent);\n if (collected.size === 0) {\n collected.add(category);\n }\n\n return [...collected].slice(0, 10);\n}\n\nfunction normalizePersonality(value: unknown): SoulPersonality {\n const source = isRecord(value) ? value : {};\n\n return {\n tone: typeof source.tone === 'string' && VALID_TONE_VALUES.includes(source.tone as SoulPersonality['tone'])\n ? (source.tone as SoulPersonality['tone'])\n : DEFAULT_PERSONALITY.tone,\n formality: typeof source.formality === 'string' && VALID_FORMALITY_VALUES.includes(source.formality as SoulPersonality['formality'])\n ? (source.formality as SoulPersonality['formality'])\n : DEFAULT_PERSONALITY.formality,\n humor: typeof source.humor === 'string' && VALID_HUMOR_VALUES.includes(source.humor as SoulPersonality['humor'])\n ? (source.humor as SoulPersonality['humor'])\n : DEFAULT_PERSONALITY.humor,\n verbosity: typeof source.verbosity === 'string' && VALID_VERBOSITY_VALUES.includes(source.verbosity as SoulPersonality['verbosity'])\n ? (source.verbosity as SoulPersonality['verbosity'])\n : DEFAULT_PERSONALITY.verbosity\n };\n}\n\nfunction normalizeDescription(value: unknown, soulContent: string): string {\n const description = normalizeString(value);\n return description ? description.slice(0, 512) : extractDescription(soulContent).slice(0, 512);\n}\n\nfunction normalizeDisplayName(value: unknown): string {\n return normalizeString(value).slice(0, 128);\n}\n\nfunction normalizeLanguages(value: unknown, soulContent: string): string[] {\n const collected = new Set<string>();\n\n if (Array.isArray(value)) {\n for (const entry of value) {\n if (typeof entry !== 'string') {\n continue;\n }\n\n const language = entry.trim();\n if (!language) {\n continue;\n }\n\n try {\n const canonical = Intl.getCanonicalLocales(language)[0];\n if (canonical) {\n collected.add(canonical);\n }\n } catch {\n continue;\n }\n }\n }\n\n if (collected.size === 0) {\n for (const language of inferLanguages(soulContent)) {\n const normalized = language.trim();\n if (!normalized) {\n continue;\n }\n\n try {\n const canonical = Intl.getCanonicalLocales(normalized)[0];\n if (canonical) {\n collected.add(canonical);\n }\n } catch {\n continue;\n }\n }\n }\n\n return [...collected];\n}\n\nfunction normalizeConfidence(value: unknown, fallback: number): number {\n return typeof value === 'number' && Number.isFinite(value) ? value : fallback;\n}\n\nfunction buildKeywordResult(soulContent: string) {\n const category = inferCategory(soulContent);\n const tags = normalizeTags([], soulContent);\n\n return {\n category,\n tags,\n personality: inferPersonality(soulContent),\n description: extractDescription(soulContent).slice(0, 512),\n displayName: extractDisplayName(soulContent) ?? '',\n languages: inferLanguages(soulContent),\n method: 'keyword' as const,\n confidence: 0.4\n };\n}\n\nfunction buildAiResult(soulContent: string, payload: unknown) {\n const analysis = isRecord(payload) ? (payload as AnalyzeApiPayload) : {};\n\n return {\n category: normalizeValidCategory(analysis.category, soulContent),\n tags: normalizeTags(analysis.tags, soulContent),\n personality: normalizePersonality(analysis.personality),\n description: normalizeDescription(analysis.description, soulContent),\n displayName: normalizeDisplayName(analysis.displayName),\n languages: normalizeLanguages(analysis.languages, soulContent),\n method: 'ai' as const,\n confidence: normalizeConfidence(analysis.confidence, 0.8)\n };\n}\n\nfunction getAnalyzeInput(first: unknown, second: unknown): AnalyzeToolInput {\n if (isRecord(second) && typeof second.soulContent === 'string') {\n return second as AnalyzeToolInput;\n }\n\n if (isRecord(first) && typeof first.soulContent === 'string') {\n return first as AnalyzeToolInput;\n }\n\n return { soulContent: '' };\n}\n\nexport function createAnalyzeTool(config: PluginConfig, logger: PluginLogger): AnalyzeToolDefinition {\n return {\n name: 'soul_analyze',\n label: 'Soul Analyze',\n description: 'Analyze a SOUL.md snippet and infer category, tags, personality, languages, and summary metadata.',\n parameters: AnalyzeParametersSchema,\n execute: async (...args: [unknown, unknown?]): Promise<ToolResult> => {\n const input = getAnalyzeInput(args[0], args[1]);\n const soulContent = input.soulContent.trim();\n\n if (soulContent.length < ANALYZE_MIN_LENGTH) {\n return toolError(\n `SOUL 内容至少需要 ${ANALYZE_MIN_LENGTH} 个字符。`,\n '请提供更完整的 SOUL.md 内容后重试。'\n );\n }\n\n if (soulContent.length > ANALYZE_MAX_LENGTH) {\n return toolError(\n `SOUL 内容不能超过 ${ANALYZE_MAX_LENGTH} 个字符。`,\n '请截取一段较短的 SOUL.md 内容后重试。'\n );\n }\n\n const url = createApiUrl(config, '/api/v1/tools/analyze-soul');\n\n try {\n const response = await fetchApi(url, {\n method: 'POST',\n body: { soulContent },\n timeout: ANALYZE_TIMEOUT_MS,\n apiToken: config.apiToken\n });\n const payload = await response.json();\n logger.info('soul_analyze: completed with ai method');\n return toolJsonResult(buildAiResult(soulContent, payload));\n } catch (error) {\n if (error instanceof ApiRequestError && error.status === 429) {\n logger.warn('soul_analyze: rate limited by registry api');\n return toolError('分析请求已触发速率限制。', '请稍后再试,或在配置中使用可用的 API Token。');\n }\n\n if (error instanceof ApiRequestError && (error.status === 0 || error.status === 408)) {\n logger.warn(`soul_analyze: fallback to keyword method due to api error status=${error.status}`);\n return toolJsonResult(buildKeywordResult(soulContent));\n }\n\n if (error instanceof ApiRequestError) {\n logger.error(`soul_analyze failed with api status=${error.status}: ${error.message}`);\n return toolError(\n formatErrorMessage(error, '分析 SOUL 内容'),\n '请稍后重试,或确认 Registry 地址与 API Token 配置正确。'\n );\n }\n\n const message = error instanceof Error ? `${error.name}: ${error.message}` : String(error);\n logger.warn(`soul_analyze: fallback to keyword method due to unexpected error: ${message}`);\n return toolJsonResult(buildKeywordResult(soulContent));\n }\n }\n };\n}\n","export interface ToolTextContent {\n type: 'text';\n text: string;\n}\n\nexport interface ToolResult {\n content: ToolTextContent[];\n details: Record<string, unknown>;\n}\n\nfunction buildTextResult(text: string): ToolResult {\n return {\n details: {},\n content: [\n {\n type: 'text',\n text\n }\n ]\n };\n}\n\nexport function toolResult(text: string): ToolResult {\n return buildTextResult(text);\n}\n\nexport function toolJsonResult(data: unknown): ToolResult {\n return buildTextResult(JSON.stringify(data, null, 2));\n}\n\nexport function toolError(message: string, suggestion?: string): ToolResult {\n const parts = [`原因:${message}`];\n\n if (suggestion?.trim()) {\n parts.push(`建议:${suggestion.trim()}`);\n }\n\n return buildTextResult(parts.join('\\n'));\n}\n","import { access, mkdir, readdir, readFile, writeFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { basename, dirname, join, relative, resolve, sep } from 'node:path';\n\nimport { Type, type Static } from '@sinclair/typebox';\nimport type { PluginLogger } from 'openclaw/plugin-sdk';\nimport { DEFAULT_PERSONALITY, type SoulPackageMeta } from '@souls-market/shared';\nimport {\n extractDescription,\n extractDisplayName,\n inferCategory,\n inferLanguages,\n inferPersonality,\n inferTags\n} from '@souls-market/shared/infer';\nimport { SECURITY_SCAN_RULES, scanSoulPackage, type ScanRule } from '@souls-market/shared/scanner';\n\nimport type { PluginConfig } from '../http';\nimport { toolError, toolJsonResult, type ToolResult } from '../result';\n\nconst DEFAULT_COMPATIBILITY = {\n openclaw: '>=0.5.0',\n models: ['anthropic/claude-*', 'openai/gpt-4*']\n} as const;\nconst DEFAULT_AUTHOR_NAME = 'openclaw-agent';\nconst DEFAULT_EXPORT_DIR_NAME = 'exported-soul';\nconst DEFAULT_LICENSE = 'UNLICENSED';\nconst SOUL_FILE_NAMES = ['SOUL.md', 'IDENTITY.md', 'AGENTS.md', 'TOOLS.md', 'USER.md', 'BOOTSTRAP.md'] as const;\nconst VALID_AGENT_ID_PATTERN = /^[A-Za-z0-9_-]+$/;\nconst REDACTED_TOKEN = '[REDACTED]';\n\nconst ExportParametersSchema = Type.Object(\n {\n agentId: Type.Optional(Type.String()),\n includeUserMd: Type.Optional(Type.Boolean())\n },\n {\n additionalProperties: false\n }\n);\n\nexport type ExportToolInput = Static<typeof ExportParametersSchema>;\n\nexport interface PluginRuntimeApi {\n agent?: {\n resolveAgentWorkspaceDir?: (agentId?: string) => string | Promise<string>;\n };\n resolveAgentWorkspaceDir?: (agentId?: string) => string | Promise<string>;\n resolveCurrentWorkspaceDir?: () => string | Promise<string>;\n}\n\nexport interface ExportToolDeps {\n homeDir?: () => string;\n}\n\nexport interface ExportedFileEntry {\n fileName: string;\n source: 'workspace' | 'generated';\n}\n\nexport interface ExcludedFileEntry {\n fileName: string;\n reason: string;\n}\n\nexport interface SecurityWarning {\n level: string;\n rule: string;\n message: string;\n path?: string;\n line?: number;\n match?: string;\n category?: string;\n}\n\nexport interface ExportToolDefinition {\n name: 'soul_export';\n label: string;\n description: string;\n parameters: typeof ExportParametersSchema;\n execute: (...args: [unknown, unknown?]) => Promise<ToolResult>;\n}\n\ntype SoulFileName = (typeof SOUL_FILE_NAMES)[number];\ntype SanitizedFile = {\n content: string;\n securityWarnings: SecurityWarning[];\n redacted: boolean;\n};\n\nasync function exists(targetPath: string): Promise<boolean> {\n try {\n await access(targetPath);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction getHomeDir(deps: ExportToolDeps): string {\n return deps.homeDir?.() ?? homedir();\n}\n\nfunction getOpenClawRoot(deps: ExportToolDeps): string {\n return join(getHomeDir(deps), '.openclaw');\n}\n\nasync function resolveCurrentWorkspaceDir(runtimeApi: PluginRuntimeApi, deps: ExportToolDeps): Promise<string> {\n const fallback = resolve(getOpenClawRoot(deps), 'workspace');\n const candidates = [\n runtimeApi.agent?.resolveAgentWorkspaceDir,\n runtimeApi.resolveAgentWorkspaceDir,\n runtimeApi.resolveCurrentWorkspaceDir\n ];\n\n for (const candidate of candidates) {\n if (typeof candidate !== 'function') {\n continue;\n }\n\n try {\n const resolvedPath = await candidate();\n if (typeof resolvedPath === 'string' && resolvedPath.trim()) {\n return resolve(resolvedPath);\n }\n } catch {\n continue;\n }\n }\n\n return fallback;\n}\n\nfunction resolveAgentWorkspaceDir(agentId: string, deps: ExportToolDeps): string {\n return resolve(getOpenClawRoot(deps), 'agents', agentId, 'workspace');\n}\n\nfunction isPathInside(parentDir: string, targetPath: string): boolean {\n const normalizedParent = resolve(parentDir);\n const normalizedTarget = resolve(targetPath);\n const relativePath = relative(normalizedParent, normalizedTarget);\n\n return relativePath === '' || (!relativePath.startsWith(`..${sep}`) && relativePath !== '..' && !relativePath.includes(`..${sep}`));\n}\n\nfunction ensureValidAgentId(agentId: string): string {\n if (!VALID_AGENT_ID_PATTERN.test(agentId)) {\n throw new Error('agentId 格式不合法,只允许字母、数字、下划线和短横线。');\n }\n\n return agentId;\n}\n\nfunction resolveExportDir(workspaceDir: string): string {\n return resolve(dirname(workspaceDir), DEFAULT_EXPORT_DIR_NAME);\n}\n\nfunction slugify(input: string): string {\n return input\n .trim()\n .replace(/['\"]/g, '')\n .replace(/([a-z0-9])([A-Z])/g, '$1-$2')\n .replace(/[^a-zA-Z0-9]+/g, '-')\n .replace(/-{2,}/g, '-')\n .replace(/^-|-$/g, '')\n .toLowerCase();\n}\n\nfunction toTitleCase(input: string): string {\n return input\n .split(/[-_\\s]+/)\n .filter(Boolean)\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())\n .join(' ');\n}\n\nasync function ensureEmptyOutputDir(outputDir: string): Promise<void> {\n try {\n const entries = await readdir(outputDir);\n if (entries.length > 0) {\n throw new Error(`导出目录已存在且非空:${outputDir}`);\n }\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {\n throw error;\n }\n }\n\n await mkdir(outputDir, { recursive: true });\n}\n\nasync function readWorkspaceFiles(workspaceDir: string): Promise<Record<SoulFileName, string | null>> {\n const files = {} as Record<SoulFileName, string | null>;\n\n for (const fileName of SOUL_FILE_NAMES) {\n try {\n files[fileName] = await readFile(join(workspaceDir, fileName), 'utf8');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n files[fileName] = null;\n continue;\n }\n\n throw error;\n }\n }\n\n return files;\n}\n\nfunction getRulePattern(rule: ScanRule): RegExp {\n const flags = rule.pattern.flags.includes('g') ? rule.pattern.flags : `${rule.pattern.flags}g`;\n return new RegExp(rule.pattern.source, flags);\n}\n\nfunction countLineNumber(content: string, index: number): number {\n return content.slice(0, index).split(/\\r?\\n/).length;\n}\n\nfunction buildSecretWarnings(fileName: string, content: string): SecurityWarning[] {\n const warnings: SecurityWarning[] = [];\n\n for (const rule of SECURITY_SCAN_RULES) {\n if (rule.category !== 'secrets-detection') {\n continue;\n }\n\n const matcher = getRulePattern(rule);\n for (const match of content.matchAll(matcher)) {\n const matchIndex = match.index ?? 0;\n warnings.push({\n level: rule.severity,\n rule: rule.id,\n message: `${rule.message}(已在导出内容中脱敏)`,\n path: fileName,\n line: countLineNumber(content, matchIndex),\n match: REDACTED_TOKEN,\n category: rule.category\n });\n }\n }\n\n return warnings;\n}\n\nfunction redactSecrets(content: string): string {\n let sanitized = content;\n\n for (const rule of SECURITY_SCAN_RULES) {\n if (rule.category !== 'secrets-detection') {\n continue;\n }\n\n sanitized = sanitized.replace(getRulePattern(rule), REDACTED_TOKEN);\n }\n\n return sanitized;\n}\n\nfunction sanitizeFileContent(fileName: string, content: string): SanitizedFile {\n const securityWarnings = buildSecretWarnings(fileName, content);\n const sanitizedContent = redactSecrets(content);\n\n return {\n content: sanitizedContent,\n securityWarnings,\n redacted: securityWarnings.length > 0\n };\n}\n\nfunction buildSoulJson(params: {\n agentId?: string;\n workspaceDir: string;\n soulContent: string;\n identityContent: string | null;\n}): SoulPackageMeta {\n const displayName = extractDisplayName(params.identityContent ?? '')?.trim();\n const name = slugify(displayName || params.agentId || basename(dirname(params.workspaceDir)) || 'exported-soul') || 'exported-soul';\n\n return {\n name,\n displayName: displayName || toTitleCase(name),\n version: '0.1.0',\n description: extractDescription(params.soulContent),\n author: {\n name: params.agentId || DEFAULT_AUTHOR_NAME\n },\n license: DEFAULT_LICENSE,\n tags: inferTags(params.soulContent),\n category: inferCategory(params.soulContent),\n compatibility: {\n openclaw: DEFAULT_COMPATIBILITY.openclaw,\n models: [...DEFAULT_COMPATIBILITY.models]\n },\n languages: inferLanguages(params.soulContent),\n personality: inferPersonality(params.soulContent) ?? DEFAULT_PERSONALITY\n };\n}\n\nasync function writeExportFile(outputDir: string, fileName: string, content: string): Promise<void> {\n await writeFile(join(outputDir, fileName), content, 'utf8');\n}\n\nfunction mapSecurityWarnings(scanResult: Awaited<ReturnType<typeof scanSoulPackage>>): SecurityWarning[] {\n return scanResult.diagnostics.map((diagnostic) => ({\n level: diagnostic.level,\n rule: diagnostic.rule,\n message: diagnostic.message,\n path: diagnostic.path,\n line: diagnostic.line,\n match: diagnostic.category === 'secrets-detection' && diagnostic.match ? REDACTED_TOKEN : diagnostic.match,\n category: diagnostic.category\n }));\n}\n\nfunction getExportInput(first: unknown, second: unknown): ExportToolInput {\n const isRecord = (value: unknown): value is ExportToolInput => Boolean(value) && typeof value === 'object' && !Array.isArray(value);\n\n if (isRecord(second)) {\n return second;\n }\n\n if (isRecord(first)) {\n return first;\n }\n\n return {};\n}\n\nfunction formatExportError(error: unknown): string {\n if (error instanceof Error) {\n const code = (error as NodeJS.ErrnoException).code;\n if (code === 'EACCES' || code === 'EPERM') {\n return '导出失败:文件系统权限不足。';\n }\n\n if (code === 'ENOSPC') {\n return '导出失败:磁盘空间不足。';\n }\n\n if (code === 'ENOENT') {\n return '导出失败:工作区文件在导出过程中不存在或已被移除。';\n }\n\n return `导出失败:${error.message}`;\n }\n\n return `导出失败:${String(error)}`;\n}\n\nexport function createExportTool(\n _config: PluginConfig,\n runtimeApi: PluginRuntimeApi = {},\n logger: PluginLogger,\n deps: ExportToolDeps = {}\n): ExportToolDefinition {\n return {\n name: 'soul_export',\n label: 'Soul Export',\n description: 'Export the current OpenClaw agent workspace as a SOUL package.',\n parameters: ExportParametersSchema,\n execute: async (...args: [unknown, unknown?]): Promise<ToolResult> => {\n try {\n const input = getExportInput(args[0], args[1]);\n const rawAgentId = input.agentId?.trim();\n const agentId = rawAgentId ? ensureValidAgentId(rawAgentId) : undefined;\n const includeUserMd = input.includeUserMd === true;\n const workspaceDir = agentId\n ? resolveAgentWorkspaceDir(agentId, deps)\n : await resolveCurrentWorkspaceDir(runtimeApi, deps);\n const openClawRoot = getOpenClawRoot(deps);\n\n if (agentId && !isPathInside(openClawRoot, workspaceDir)) {\n logger.warn(`soul_export rejected agentId due to unsafe path: ${agentId}`);\n return toolError('agentId 解析出的工作区路径超出了允许范围。', '请检查 agentId 是否正确。');\n }\n\n if (!(await exists(join(workspaceDir, 'SOUL.md')))) {\n logger.warn(`soul_export missing SOUL.md in workspace: ${workspaceDir}`);\n return toolError('当前工作区未找到 SOUL 配置文件', '请确认 agentId 是否正确,或先在工作区中创建 SOUL.md。');\n }\n\n const outputDir = resolveExportDir(workspaceDir);\n await ensureEmptyOutputDir(outputDir);\n\n const workspaceFiles = await readWorkspaceFiles(workspaceDir);\n const exportedFiles: ExportedFileEntry[] = [];\n const excludedFiles: ExcludedFileEntry[] = [];\n const preScanWarnings: SecurityWarning[] = [];\n const sanitizedWorkspaceFiles = { ...workspaceFiles };\n\n for (const fileName of SOUL_FILE_NAMES) {\n if (fileName === 'USER.md' && !includeUserMd && workspaceFiles[fileName] !== null) {\n excludedFiles.push({\n fileName,\n reason: '默认排除 USER.md 以保护用户隐私'\n });\n continue;\n }\n\n const content = workspaceFiles[fileName];\n if (content === null) {\n continue;\n }\n\n const sanitizedFile = sanitizeFileContent(fileName, content);\n sanitizedWorkspaceFiles[fileName] = sanitizedFile.content;\n preScanWarnings.push(...sanitizedFile.securityWarnings);\n await writeExportFile(outputDir, fileName, sanitizedFile.content);\n exportedFiles.push({ fileName, source: 'workspace' });\n }\n\n const soulJson = buildSoulJson({\n agentId,\n workspaceDir,\n soulContent: sanitizedWorkspaceFiles['SOUL.md'] ?? '',\n identityContent: sanitizedWorkspaceFiles['IDENTITY.md']\n });\n const soulJsonText = `${JSON.stringify(soulJson, null, 2)}\\n`;\n const sanitizedSoulJson = sanitizeFileContent('soul.json', soulJsonText);\n preScanWarnings.push(...sanitizedSoulJson.securityWarnings);\n await writeExportFile(outputDir, 'soul.json', sanitizedSoulJson.content);\n exportedFiles.push({ fileName: 'soul.json', source: 'generated' });\n const exportedSoulJson = JSON.parse(sanitizedSoulJson.content) as SoulPackageMeta;\n\n const securityWarnings: SecurityWarning[] = [...preScanWarnings];\n let securityScanPassed = preScanWarnings.every((warning) => warning.level !== 'error');\n if (preScanWarnings.length > 0) {\n logger.warn(`soul_export detected ${preScanWarnings.length} pre-scan warning(s)`);\n }\n\n try {\n const scanResult = await scanSoulPackage(outputDir);\n securityWarnings.push(...mapSecurityWarnings(scanResult));\n securityScanPassed = securityScanPassed && scanResult.passed;\n if (!scanResult.passed || scanResult.diagnostics.length > 0) {\n logger.warn(`soul_export scan diagnostics: ${scanResult.diagnostics.length}`);\n }\n } catch (error) {\n securityScanPassed = false;\n const message = error instanceof Error ? error.message : String(error);\n logger.error(`soul_export scan failed: ${message}`);\n securityWarnings.push({\n level: 'warn',\n rule: 'security-scan-failed',\n message: `安全扫描失败:${message}`\n });\n }\n\n const payload = {\n workspaceDir,\n outputDir,\n files: exportedFiles,\n excluded: excludedFiles,\n securityWarnings,\n securityScanPassed,\n soulJson: exportedSoulJson\n };\n logger.info(\n `soul_export completed: workspace=${workspaceDir}, output=${outputDir}, files=${exportedFiles.length}, excluded=${excludedFiles.length}, warnings=${securityWarnings.length}`\n );\n return toolJsonResult(payload);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n logger.error(`soul_export failed: ${message}`);\n return toolError(formatExportError(error), '请检查工作区路径、导出目录权限或磁盘空间后重试。');\n }\n }\n };\n}\n","import { Type, type Static } from '@sinclair/typebox';\nimport type { PluginLogger } from 'openclaw/plugin-sdk';\n\nimport { ApiRequestError, createApiUrl, fetchApi, formatErrorMessage, type PluginConfig } from '../http';\nimport { toolError, toolJsonResult, toolResult, type ToolResult } from '../result';\n\nconst SEARCH_TIMEOUT_MS = 10_000;\n\nconst SearchParametersSchema = Type.Object(\n {\n query: Type.String(),\n category: Type.Optional(Type.String())\n },\n {\n additionalProperties: false\n }\n);\n\nexport type SearchToolInput = Static<typeof SearchParametersSchema>;\n\nexport interface SearchToolDefinition {\n name: 'soul_search';\n label: string;\n description: string;\n parameters: typeof SearchParametersSchema;\n execute: (...args: [unknown, unknown?]) => Promise<ToolResult>;\n}\n\ninterface SearchApiItem {\n name?: unknown;\n displayName?: unknown;\n description?: unknown;\n category?: unknown;\n tags?: unknown;\n rating?: unknown;\n ratingAvg?: unknown;\n downloads?: unknown;\n}\n\ninterface SearchApiResponse {\n data?: unknown;\n pagination?: {\n page?: unknown;\n perPage?: unknown;\n total?: unknown;\n totalPages?: unknown;\n };\n total?: unknown;\n query?: unknown;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction normalizeString(value: unknown): string {\n return typeof value === 'string' ? value.trim() : '';\n}\n\nfunction normalizeStringArray(value: unknown): string[] {\n if (!Array.isArray(value)) {\n return [];\n }\n\n return value\n .map((item) => normalizeString(item))\n .filter((item) => item.length > 0);\n}\n\nfunction normalizeNumber(value: unknown): number | null {\n if (typeof value === 'number' && Number.isFinite(value)) {\n return value;\n }\n\n if (typeof value === 'string' && value.trim()) {\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : null;\n }\n\n return null;\n}\n\nfunction normalizeSearchItem(item: SearchApiItem) {\n const ratingSource = item.ratingAvg ?? item.rating;\n\n return {\n name: normalizeString(item.name),\n displayName: normalizeString(item.displayName),\n description: normalizeString(item.description),\n category: normalizeString(item.category),\n tags: normalizeStringArray(item.tags),\n rating: normalizeNumber(ratingSource),\n downloads: normalizeNumber(item.downloads) ?? 0\n };\n}\n\nfunction normalizeSearchResponse(payload: unknown) {\n const response = isRecord(payload) ? (payload as SearchApiResponse) : {};\n const items = Array.isArray(response.data) ? response.data : [];\n const pagination = {\n page: normalizeNumber(response.pagination?.page) ?? 1,\n perPage: normalizeNumber(response.pagination?.perPage) ?? 20,\n total: normalizeNumber(response.pagination?.total) ?? normalizeNumber(response.total) ?? 0,\n totalPages: normalizeNumber(response.pagination?.totalPages) ?? 0\n };\n\n return {\n results: items\n .filter(isRecord)\n .map((item) => normalizeSearchItem(item)),\n total: pagination.total,\n pagination,\n query: normalizeString(response.query)\n };\n}\n\nfunction getSearchInput(first: unknown, second: unknown): SearchToolInput {\n if (isRecord(second) && typeof second.query === 'string') {\n return second as SearchToolInput;\n }\n\n if (isRecord(first) && typeof first.query === 'string') {\n return first as SearchToolInput;\n }\n\n return { query: '' };\n}\n\nfunction stringifyError(error: unknown): string {\n if (error instanceof Error) {\n return `${error.name}: ${error.message}`;\n }\n\n return String(error);\n}\n\nexport function createSearchTool(config: PluginConfig, logger: PluginLogger): SearchToolDefinition {\n return {\n name: 'soul_search',\n label: 'Soul Search',\n description: 'Search Soul Market for matching SOUL templates by keyword and category.',\n parameters: SearchParametersSchema,\n execute: async (...args: [unknown, unknown?]): Promise<ToolResult> => {\n const input = getSearchInput(args[0], args[1]);\n const query = input.query.trim();\n const category = typeof input.category === 'string' ? input.category.trim() : '';\n\n if (!query) {\n return toolError('搜索词不能为空。', '请输入一个不超过 200 个字符的查询词。');\n }\n\n if (query.length > 200) {\n return toolError('搜索词长度不能超过 200 个字符。', '请缩短查询词后重试。');\n }\n\n try {\n const url = createApiUrl(\n config,\n '/api/v1/search',\n {\n q: query,\n ...(category ? { category } : {})\n }\n );\n\n const response = await fetchApi(url, {\n method: 'GET',\n timeout: SEARCH_TIMEOUT_MS\n });\n const payload = await response.json();\n const normalized = normalizeSearchResponse(payload);\n\n if (normalized.results.length === 0) {\n logger.info(`soul_search: no results for query=\"${query}\" category=\"${category || 'all'}\"`);\n return toolResult('未找到匹配的 SOUL 模板');\n }\n\n logger.info(`soul_search: found ${normalized.results.length} result(s) for query=\"${query}\"`);\n return toolJsonResult(normalized);\n } catch (error) {\n logger.warn(`soul_search request failed: ${stringifyError(error)}`);\n\n if (error instanceof ApiRequestError && error.status === 429) {\n return toolError('搜索请求过于频繁,已触发速率限制。', '请稍后再试。');\n }\n\n return toolError(\n formatErrorMessage(error, '搜索 SOUL 模板'),\n '请确认 Registry 地址配置正确并检查网络连接。'\n );\n }\n }\n };\n}\n"],"mappings":"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","names":["Type","DEFAULT_PERSONALITY","extractDescription","extractDisplayName","inferCategory","inferLanguages","inferPersonality","inferTags","Type","extractDisplayName","extractDescription","inferTags","inferCategory","inferLanguages","inferPersonality","DEFAULT_PERSONALITY","isRecord","Type","Type","isRecord","normalizeString"]}
@@ -0,0 +1,42 @@
1
+ {
2
+ "id": "soul-market",
3
+ "name": "Soul Market",
4
+ "description": "Search, export, and analyze SOUL packages from OpenClaw.",
5
+ "configSchema": {
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "properties": {
9
+ "registryUrl": {
10
+ "type": "string",
11
+ "format": "uri",
12
+ "default": "https://api.souls.market",
13
+ "description": "Soul Market Registry API base URL."
14
+ },
15
+ "apiToken": {
16
+ "description": "Optional Soul Market API token or SecretRef.",
17
+ "oneOf": [
18
+ {
19
+ "type": "string",
20
+ "minLength": 1
21
+ },
22
+ {
23
+ "type": "object",
24
+ "additionalProperties": false,
25
+ "properties": {
26
+ "source": {
27
+ "type": "string"
28
+ },
29
+ "provider": {
30
+ "type": "string"
31
+ },
32
+ "id": {
33
+ "type": "string"
34
+ }
35
+ },
36
+ "required": ["source", "provider", "id"]
37
+ }
38
+ ]
39
+ }
40
+ }
41
+ }
42
+ }
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@souls_market/openclaw-plugin",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js"
11
+ }
12
+ },
13
+ "files": [
14
+ "dist",
15
+ "openclaw.plugin.json"
16
+ ],
17
+ "openclaw": {
18
+ "extensions": [
19
+ "./dist/index.js"
20
+ ],
21
+ "pluginId": "soul-market",
22
+ "manifest": "./openclaw.plugin.json"
23
+ },
24
+ "scripts": {
25
+ "build": "tsup",
26
+ "lint": "eslint \"src/**/*.ts\"",
27
+ "typecheck": "tsc --noEmit",
28
+ "clean": "rm -rf dist"
29
+ },
30
+ "dependencies": {
31
+ "@souls-market/shared": "workspace:*"
32
+ },
33
+ "peerDependencies": {
34
+ "@sinclair/typebox": "*",
35
+ "openclaw": "*"
36
+ },
37
+ "devDependencies": {
38
+ "@sinclair/typebox": "^0.34.48",
39
+ "@types/node": "^22.10.2",
40
+ "openclaw": "2026.3.22",
41
+ "tsup": "^8.3.5",
42
+ "typescript": "^5.7.2"
43
+ }
44
+ }