@dcrays/dcgchat-test 0.2.26 → 0.2.28

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,198 @@
1
+ import axios from 'axios'
2
+ // @ts-ignore
3
+ import md5 from 'md5'
4
+ import type { IResponse } from '../types.js'
5
+ import { getUserTokenCache } from './userInfo.js'
6
+ import { getMsgParams } from '../utils/global.js'
7
+ import { ENV } from '../utils/constant.js'
8
+ import { dcgLogger } from '../utils/log.js'
9
+
10
+ export const apiUrlMap = {
11
+ production: 'https://api-gateway.shuwenda.com',
12
+ test: 'https://api-gateway.shuwenda.icu',
13
+ develop: 'https://shenyu-dev.shuwenda.icu'
14
+ }
15
+
16
+ export const appKey = {
17
+ production: '2A1C74D315CB4A01BF3DA8983695AFE2',
18
+ test: '7374A073CCBD4C8CA84FAD33896F0B69',
19
+ develop: '7374A073CCBD4C8CA84FAD33896F0B69'
20
+ }
21
+
22
+ export const signKey = {
23
+ production: '34E9023008EA445AAE6CC075CC954F46',
24
+ test: 'FE93D3322CB94E978CE95BD4AA2A37D7',
25
+ develop: 'FE93D3322CB94E978CE95BD4AA2A37D7'
26
+ }
27
+
28
+ export const version = '1.0.0'
29
+
30
+ /**
31
+ * 根据 axios 请求配置生成等价 curl,便于复制给后端排查
32
+ */
33
+ function toCurl(config: {
34
+ baseURL?: string
35
+ url?: string
36
+ method?: string
37
+ headers?: Record<string, string | number | undefined>
38
+ data?: unknown
39
+ }): string {
40
+ const base = config.baseURL ?? ''
41
+ const path = config.url ?? ''
42
+ const url = path.startsWith('http')
43
+ ? path
44
+ : `${base.replace(/\/$/, '')}/${path.replace(/^\//, '')}`
45
+ const method = (config.method ?? 'GET').toUpperCase()
46
+ const headers = config.headers ?? {}
47
+ const parts = ['curl', '-X', method, `'${url}'`]
48
+ for (const [k, v] of Object.entries(headers)) {
49
+ if (v !== undefined && v !== '') {
50
+ parts.push('-H', `'${k}: ${v}'`)
51
+ }
52
+ }
53
+ if (method !== 'GET' && config.data !== undefined) {
54
+ const body = typeof config.data === 'string' ? config.data : JSON.stringify(config.data)
55
+ parts.push('-d', `'${body.replace(/'/g, "'\\''")}'`)
56
+ }
57
+ return parts.join(' ')
58
+ }
59
+
60
+ /**
61
+ * 生成签名
62
+ * @param {Object} body 请求体
63
+ * @param {number} timestamp 时间戳
64
+ * @param {string} path 请求地址
65
+ * @param {'production' | 'test' | 'develop'} ENV 请求环境
66
+ * @param {string} version 版本号
67
+ * @returns {string} 大写 MD5 签名
68
+ */
69
+ export function getSignature(
70
+ body: Record<string, unknown>,
71
+ timestamp: number,
72
+ path: string,
73
+ ENV: 'production' | 'test' | 'develop',
74
+ version: string = '1.0.0'
75
+ ) {
76
+ // 1. 构造 map
77
+ const map = { timestamp, path, version, ...body }
78
+ // 2. 按 key 进行自然排序
79
+ const sortedKeys = Object.keys(map).sort()
80
+ // 3. 拼接 key + value
81
+ const signStr =
82
+ sortedKeys
83
+ .map((key) => {
84
+ const val = map[key as keyof typeof map]
85
+ return val === undefined
86
+ ? ''
87
+ : `${key}${typeof val === 'object' ? JSON.stringify(val) : val}`
88
+ })
89
+ .join('') + signKey[ENV]
90
+ // 4. MD5 加密并转大写
91
+ return md5(signStr).toUpperCase()
92
+ }
93
+
94
+ function buildHeaders(data: Record<string, unknown>, url: string, userToken?: string) {
95
+ const timestamp = Date.now()
96
+
97
+ const headers: Record<string, string | number> = {
98
+ 'Content-Type': 'application/json',
99
+ appKey: appKey[ENV],
100
+ sign: getSignature(data, timestamp, url, ENV, version),
101
+ timestamp,
102
+ version
103
+ }
104
+
105
+ // 如果提供了 userToken,添加到 headers
106
+ if (userToken) {
107
+ headers.authorization = userToken
108
+ }
109
+
110
+ return headers
111
+ }
112
+
113
+ const axiosInstance = axios.create({
114
+ baseURL: apiUrlMap[ENV],
115
+ timeout: 10000
116
+ })
117
+
118
+ // 请求拦截器:自动注入 userToken
119
+ axiosInstance.interceptors.request.use(
120
+ (config) => {
121
+ // 如果请求配置中已经有 authorization,优先使用
122
+ if (config.headers?.authorization) {
123
+ return config
124
+ }
125
+
126
+ // 从请求上下文中获取 botToken(需要在调用时设置)
127
+ const botToken = (config as any).__botToken as string | undefined
128
+ if (botToken) {
129
+ const cachedToken = getUserTokenCache(botToken)
130
+ if (cachedToken) {
131
+ config.headers = config.headers || {}
132
+ config.headers.authorization = cachedToken
133
+ dcgLogger(
134
+ `[request] auto-injected userToken from cache for botToken=${botToken.slice(0, 10)}...`
135
+ )
136
+ }
137
+ }
138
+
139
+ return config
140
+ },
141
+ (error) => {
142
+ return Promise.reject(error)
143
+ }
144
+ )
145
+
146
+ // 响应拦截器:打印 curl 便于调试
147
+ axiosInstance.interceptors.response.use(
148
+ (response) => {
149
+ return response.data
150
+ },
151
+ (error) => {
152
+ const config = error.config ?? {}
153
+ const curl = toCurl(config)
154
+ dcgLogger(`[request] curl for backend (failed request): ${curl}`)
155
+ return Promise.reject(error)
156
+ }
157
+ )
158
+
159
+ /**
160
+ * POST 请求(支持可选的 userToken 和 botToken)
161
+ * @param url 请求路径
162
+ * @param data 请求体
163
+ * @param options 可选配置
164
+ * @param options.userToken 直接提供的 userToken(优先级最高)
165
+ * @param options.botToken 用于从缓存获取 userToken 的 botToken
166
+ */
167
+ export function post<T = Record<string, unknown>, R = unknown>(
168
+ url: string,
169
+ data: T,
170
+ options?: {
171
+ userToken?: string
172
+ botToken?: string
173
+ }
174
+ ): Promise<IResponse<R>> {
175
+ const params = getMsgParams() || {}
176
+ const config: any = {
177
+ method: 'POST',
178
+ url,
179
+ data: {
180
+ ...data,
181
+ _appId: params.appId
182
+ },
183
+ headers: buildHeaders(
184
+ {
185
+ ...data,
186
+ _appId: params.appId
187
+ } as Record<string, unknown>,
188
+ url,
189
+ options?.userToken
190
+ )
191
+ }
192
+
193
+ // 将 botToken 附加到配置中,供请求拦截器使用
194
+ if (options?.botToken) {
195
+ config.__botToken = options.botToken
196
+ }
197
+ return axiosInstance.request(config)
198
+ }
@@ -3,16 +3,18 @@
3
3
  * 负责维护 botToken -> userToken 的映射关系,支持自动过期
4
4
  */
5
5
 
6
+ import { dcgLogger } from '../utils/log.js'
7
+
6
8
  // userToken 缓存配置
7
- const TOKEN_CACHE_DURATION = 60 * 60 * 1000; // 1小时
9
+ const TOKEN_CACHE_DURATION = 60 * 60 * 1000 // 1小时
8
10
 
9
11
  type TokenCacheEntry = {
10
- token: string;
11
- expiresAt: number;
12
- };
12
+ token: string
13
+ expiresAt: number
14
+ }
13
15
 
14
16
  // 内存缓存:botToken -> { token, expiresAt }
15
- const tokenCache = new Map<string, TokenCacheEntry>();
17
+ const tokenCache = new Map<string, TokenCacheEntry>()
16
18
 
17
19
  /**
18
20
  * 设置 userToken 缓存
@@ -20,11 +22,11 @@ const tokenCache = new Map<string, TokenCacheEntry>();
20
22
  * @param userToken 用户 token
21
23
  */
22
24
  export function setUserTokenCache(botToken: string, userToken: string): void {
23
- const expiresAt = Date.now() + TOKEN_CACHE_DURATION;
24
- tokenCache.set(botToken, { token: userToken, expiresAt });
25
- console.log(
26
- `[token-cache] cached userToken for botToken=${botToken.slice(0, 10)}..., expires at ${new Date(expiresAt).toISOString()}`,
27
- );
25
+ const expiresAt = Date.now() + TOKEN_CACHE_DURATION
26
+ tokenCache.set(botToken, { token: userToken, expiresAt })
27
+ dcgLogger(
28
+ `[token-cache] cached userToken for botToken=${botToken.slice(0, 10)}..., expires at ${new Date(expiresAt).toISOString()}`
29
+ )
28
30
  }
29
31
 
30
32
  /**
@@ -33,23 +35,23 @@ export function setUserTokenCache(botToken: string, userToken: string): void {
33
35
  * @returns userToken 或 null(未找到或已过期)
34
36
  */
35
37
  export function getUserTokenCache(botToken: string): string | null {
36
- const entry = tokenCache.get(botToken);
38
+ const entry = tokenCache.get(botToken)
37
39
  if (!entry) {
38
- console.log(`[token-cache] no cache found for botToken=${botToken.slice(0, 10)}...`);
39
- return null;
40
+ dcgLogger(`[token-cache] no cache found for botToken=${botToken.slice(0, 10)}...`)
41
+ return null
40
42
  }
41
43
 
42
44
  // 检查是否过期
43
45
  if (Date.now() >= entry.expiresAt) {
44
- console.log(`[token-cache] cache expired for botToken=${botToken.slice(0, 10)}..., removing`);
45
- tokenCache.delete(botToken);
46
- return null;
46
+ dcgLogger(`[token-cache] cache expired for botToken=${botToken.slice(0, 10)}..., removing`)
47
+ tokenCache.delete(botToken)
48
+ return null
47
49
  }
48
50
 
49
- console.log(
50
- `[token-cache] cache hit for botToken=${botToken.slice(0, 10)}..., valid until ${new Date(entry.expiresAt).toISOString()}`,
51
- );
52
- return entry.token;
51
+ dcgLogger(
52
+ `[token-cache] cache hit for botToken=${botToken.slice(0, 10)}..., valid until ${new Date(entry.expiresAt).toISOString()}`
53
+ )
54
+ return entry.token
53
55
  }
54
56
 
55
57
  /**
@@ -57,41 +59,41 @@ export function getUserTokenCache(botToken: string): string | null {
57
59
  * @param botToken 机器人 token
58
60
  */
59
61
  export function clearUserTokenCache(botToken: string): void {
60
- tokenCache.delete(botToken);
61
- console.log(`[token-cache] cleared cache for botToken=${botToken.slice(0, 10)}...`);
62
+ tokenCache.delete(botToken)
63
+ dcgLogger(`[token-cache] cleared cache for botToken=${botToken.slice(0, 10)}...`)
62
64
  }
63
65
 
64
66
  /**
65
67
  * 清除所有缓存
66
68
  */
67
69
  export function clearAllUserTokenCache(): void {
68
- tokenCache.clear();
69
- console.log(`[token-cache] cleared all token cache`);
70
+ tokenCache.clear()
71
+ dcgLogger(`[token-cache] cleared all token cache`)
70
72
  }
71
73
 
72
74
  /**
73
75
  * 获取缓存统计信息(用于调试)
74
76
  */
75
77
  export function getTokenCacheStats(): {
76
- total: number;
77
- valid: number;
78
- expired: number;
78
+ total: number
79
+ valid: number
80
+ expired: number
79
81
  } {
80
- const now = Date.now();
81
- let valid = 0;
82
- let expired = 0;
82
+ const now = Date.now()
83
+ let valid = 0
84
+ let expired = 0
83
85
 
84
86
  for (const entry of tokenCache.values()) {
85
87
  if (now < entry.expiresAt) {
86
- valid++;
88
+ valid++
87
89
  } else {
88
- expired++;
90
+ expired++
89
91
  }
90
92
  }
91
93
 
92
94
  return {
93
95
  total: tokenCache.size,
94
96
  valid,
95
- expired,
96
- };
97
+ expired
98
+ }
97
99
  }
package/src/skill.ts CHANGED
@@ -1,239 +1,161 @@
1
- /** skill utils */
2
- import axios from 'axios';
1
+ import axios from 'axios'
3
2
  /** @ts-ignore */
4
- import unzipper from 'unzipper';
5
- import { pipeline } from "stream/promises";
6
- import fs from 'fs';
7
- import path from 'path';
8
- import type { ClawdbotConfig, RuntimeEnv } from "openclaw/plugin-sdk";
9
- import { logDcgchat } from './log.js';
10
- import { getDcgchatRuntime, getWorkspaceDir } from './runtime.js';
11
- import { getWsConnection } from './connection.js';
12
- import { resolveAccount } from './channel.js';
13
- import { getMsgParams } from './tool.js';
3
+ import unzipper from 'unzipper'
4
+ import { pipeline } from 'stream/promises'
5
+ import fs from 'fs'
6
+ import path from 'path'
7
+ import { getWorkspaceDir } from './utils/global.js'
8
+ import { getWsConnection } from './utils/global.js'
9
+ import { dcgLogger } from './utils/log.js'
10
+ import { isWsOpen } from './transport.js'
14
11
 
15
12
  type ISkillParams = {
16
- path: string;
17
- code: string;
18
- }
19
-
20
- type SkillContext = {
21
- cfg: ClawdbotConfig;
22
- accountId: string;
23
- runtime?: RuntimeEnv;
13
+ path: string
14
+ code: string
24
15
  }
25
16
 
26
17
  function sendEvent(msgContent: Record<string, any>) {
27
18
  const ws = getWsConnection()
28
- if (ws?.readyState === WebSocket.OPEN) {
29
- ws.send(JSON.stringify({
30
- messageType: "openclaw_bot_event",
31
- source: "client",
32
- content: msgContent
33
- }));
34
- logDcgchat.info(`技能安装: ${JSON.stringify(msgContent)}`);
35
-
19
+ if (isWsOpen()) {
20
+ ws?.send(
21
+ JSON.stringify({
22
+ messageType: 'openclaw_bot_event',
23
+ source: 'client',
24
+ content: msgContent
25
+ })
26
+ )
27
+ dcgLogger(`技能安装: ${JSON.stringify(msgContent)}`)
36
28
  }
37
29
  }
38
30
 
39
- // async function sendNewSessionCommand(ctx: SkillContext) {
40
- // try {
41
- // const core = getDcgchatRuntime();
42
- // const log = ctx.runtime?.log ?? console.log;
43
- // const params = getMsgParams();
44
- // const account = resolveAccount(ctx.cfg, ctx.accountId);
45
- // const userId = String(params.userId);
46
-
47
- // const route = core.channel.routing.resolveAgentRoute({
48
- // cfg: ctx.cfg,
49
- // channel: "dcgchat",
50
- // accountId: account.accountId,
51
- // peer: { kind: "direct", id: userId },
52
- // });
53
-
54
- // const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(ctx.cfg);
55
- // const bodyFormatted = core.channel.reply.formatAgentEnvelope({
56
- // channel: "书灵墨宝",
57
- // from: userId,
58
- // timestamp: new Date(),
59
- // envelope: envelopeOptions,
60
- // body: "/new",
61
- // });
62
-
63
- // const ctxPayload = core.channel.reply.finalizeInboundContext({
64
- // Body: bodyFormatted,
65
- // RawBody: "/new",
66
- // CommandBody: "/new",
67
- // From: userId,
68
- // To: userId,
69
- // SessionKey: route.sessionKey,
70
- // AccountId: params.sessionId,
71
- // ChatType: "direct",
72
- // SenderName: userId,
73
- // SenderId: userId,
74
- // Provider: "dcgchat" as const,
75
- // Surface: "dcgchat" as const,
76
- // MessageSid: Date.now().toString(),
77
- // Timestamp: Date.now(),
78
- // WasMentioned: true,
79
- // CommandAuthorized: true,
80
- // OriginatingChannel: "dcgchat" as const,
81
- // OriginatingTo: `user:${userId}`,
82
- // });
83
-
84
- // const noopDispatcher = {
85
- // sendToolResult: () => false,
86
- // sendBlockReply: () => false,
87
- // sendFinalReply: () => false,
88
- // waitForIdle: async () => {},
89
- // getQueuedCounts: () => ({ tool: 0, block: 0, final: 0 }),
90
- // markComplete: () => {},
91
- // };
92
-
93
- // await core.channel.reply.withReplyDispatcher({
94
- // dispatcher: noopDispatcher,
95
- // run: () =>
96
- // core.channel.reply.dispatchReplyFromConfig({
97
- // ctx: ctxPayload,
98
- // cfg: ctx.cfg,
99
- // dispatcher: noopDispatcher,
100
- // }),
101
- // });
102
-
103
- // log(`dcgchat: /new command dispatched silently after skill install`);
104
- // } catch (err) {
105
- // logDcgchat.error(`sendNewSessionCommand failed: ${err}`);
106
- // }
107
- // }
108
-
109
- export async function installSkill(params: ISkillParams, msgContent: Record<string, any>, ctx?: SkillContext) {
110
- const { path: cdnUrl, code } = params;
111
- const workspacePath = getWorkspaceDir();
112
-
113
- const skillDir = path.join(workspacePath, 'skills', code);
114
-
31
+ export async function installSkill(params: ISkillParams, msgContent: Record<string, any>) {
32
+ const { path: cdnUrl, code } = params
33
+ const workspacePath = getWorkspaceDir()
34
+
35
+ const skillDir = path.join(workspacePath, 'skills', code)
36
+
115
37
  // 确保 skills 目录存在
116
- const skillsDir = path.join(workspacePath, 'skills');
38
+ const skillsDir = path.join(workspacePath, 'skills')
117
39
  if (!fs.existsSync(skillsDir)) {
118
- fs.mkdirSync(skillsDir, { recursive: true });
40
+ fs.mkdirSync(skillsDir, { recursive: true })
119
41
  }
120
42
  // 如果目标目录已存在,先删除
121
43
  if (fs.existsSync(skillDir)) {
122
- fs.rmSync(skillDir, { recursive: true, force: true });
44
+ fs.rmSync(skillDir, { recursive: true, force: true })
123
45
  }
124
-
46
+
125
47
  try {
126
48
  // 下载 zip 文件
127
49
  const response = await axios({
128
50
  method: 'get',
129
51
  url: cdnUrl,
130
52
  responseType: 'stream'
131
- });
53
+ })
132
54
  // 创建目标目录
133
- fs.mkdirSync(skillDir, { recursive: true });
55
+ fs.mkdirSync(skillDir, { recursive: true })
134
56
  // 解压文件到目标目录,跳过顶层文件夹
135
57
  await new Promise((resolve, reject) => {
136
- const tasks: Promise<void>[] = [];
137
- let rootDir: string | null = null;
138
- let hasError = false;
139
-
140
- response.data
141
- .pipe(unzipper.Parse())
142
- .on("entry", (entry: any) => {
143
- if (hasError) {
144
- entry.autodrain();
145
- return;
146
- }
147
-
148
- try {
149
- const flags = entry.props?.flags ?? 0;
150
- const isUtf8 = (flags & 0x800) !== 0;
151
- let entryPath: string;
152
- if (!isUtf8 && entry.props?.pathBuffer) {
153
- entryPath = new TextDecoder('gbk').decode(entry.props.pathBuffer);
154
- } else {
155
- entryPath = entry.path;
156
- }
157
- const pathParts = entryPath.split("/");
158
-
159
- // 检测根目录
160
- if (!rootDir && pathParts.length > 1) {
161
- rootDir = pathParts[0];
162
- }
163
-
164
- let newPath = entryPath;
165
-
166
- // 移除顶层文件夹
167
- if (rootDir && entryPath.startsWith(rootDir + "/")) {
168
- newPath = entryPath.slice(rootDir.length + 1);
169
- }
170
-
171
- if (!newPath) {
172
- entry.autodrain();
173
- return;
174
- }
175
-
176
- const targetPath = path.join(skillDir, newPath);
177
-
178
- if (entry.type === "Directory") {
179
- fs.mkdirSync(targetPath, { recursive: true });
180
- entry.autodrain();
181
- } else {
182
- const parentDir = path.dirname(targetPath);
183
- fs.mkdirSync(parentDir, { recursive: true });
184
- const writeStream = fs.createWriteStream(targetPath);
185
- const task = pipeline(entry, writeStream).catch((err) => {
186
- hasError = true;
187
- throw new Error(`解压文件失败 ${entryPath}: ${err.message}`);
188
- });
189
- tasks.push(task);
190
- }
191
- } catch (err) {
192
- hasError = true;
193
- entry.autodrain();
194
- reject(new Error(`处理entry失败: ${err}`));
195
- }
58
+ const tasks: Promise<void>[] = []
59
+ let rootDir: string | null = null
60
+ let hasError = false
61
+
62
+ response.data
63
+ .pipe(unzipper.Parse())
64
+ .on('entry', (entry: any) => {
65
+ if (hasError) {
66
+ entry.autodrain()
67
+ return
68
+ }
69
+
70
+ try {
71
+ const flags = entry.props?.flags ?? 0
72
+ const isUtf8 = (flags & 0x800) !== 0
73
+ let entryPath: string
74
+ if (!isUtf8 && entry.props?.pathBuffer) {
75
+ entryPath = new TextDecoder('gbk').decode(entry.props.pathBuffer)
76
+ } else {
77
+ entryPath = entry.path
78
+ }
79
+ const pathParts = entryPath.split('/')
80
+
81
+ // 检测根目录
82
+ if (!rootDir && pathParts.length > 1) {
83
+ rootDir = pathParts[0]
84
+ }
85
+
86
+ let newPath = entryPath
87
+
88
+ // 移除顶层文件夹
89
+ if (rootDir && entryPath.startsWith(rootDir + '/')) {
90
+ newPath = entryPath.slice(rootDir.length + 1)
91
+ }
92
+
93
+ if (!newPath) {
94
+ entry.autodrain()
95
+ return
96
+ }
97
+
98
+ const targetPath = path.join(skillDir, newPath)
99
+
100
+ if (entry.type === 'Directory') {
101
+ fs.mkdirSync(targetPath, { recursive: true })
102
+ entry.autodrain()
103
+ } else {
104
+ const parentDir = path.dirname(targetPath)
105
+ fs.mkdirSync(parentDir, { recursive: true })
106
+ const writeStream = fs.createWriteStream(targetPath)
107
+ const task = pipeline(entry, writeStream).catch((err) => {
108
+ hasError = true
109
+ throw new Error(`解压文件失败 ${entryPath}: ${err.message}`)
110
+ })
111
+ tasks.push(task)
112
+ }
113
+ } catch (err) {
114
+ hasError = true
115
+ entry.autodrain()
116
+ reject(new Error(`处理entry失败: ${err}`))
117
+ }
118
+ })
119
+ .on('close', async () => {
120
+ try {
121
+ await Promise.all(tasks)
122
+ resolve(null)
123
+ } catch (err) {
124
+ reject(err)
125
+ }
126
+ })
127
+ .on('error', (err: { message: any }) => {
128
+ hasError = true
129
+ reject(new Error(`解压流错误: ${err.message}`))
130
+ })
196
131
  })
197
- .on("close", async () => {
198
- try {
199
- await Promise.all(tasks);
200
- resolve(null);
201
- } catch (err) {
202
- reject(err);
203
- }
204
- })
205
- .on("error", (err: { message: any; }) => {
206
- hasError = true;
207
- reject(new Error(`解压流错误: ${err.message}`));
208
- });
209
- });
210
- sendEvent({ ...msgContent, status: 'ok' })
211
- // if (ctx) {
212
- // await sendNewSessionCommand(ctx);
213
- // }
132
+ sendEvent({ ...msgContent, status: 'ok' })
214
133
  } catch (error) {
215
134
  // 如果安装失败,清理目录
216
135
  if (fs.existsSync(skillDir)) {
217
- fs.rmSync(skillDir, { recursive: true, force: true });
136
+ fs.rmSync(skillDir, { recursive: true, force: true })
218
137
  }
219
138
  sendEvent({ ...msgContent, status: 'fail' })
220
139
  }
221
140
  }
222
141
 
223
- export function uninstallSkill(params: Omit<ISkillParams, 'path'>, msgContent: Record<string, any>) {
224
- const { code } = params;
225
-
226
- const workspacePath = getWorkspaceDir();
142
+ export function uninstallSkill(
143
+ params: Omit<ISkillParams, 'path'>,
144
+ msgContent: Record<string, any>
145
+ ) {
146
+ const { code } = params
147
+
148
+ const workspacePath = getWorkspaceDir()
227
149
  if (!workspacePath) {
228
150
  sendEvent({ ...msgContent, status: 'ok' })
229
151
  }
230
-
231
- const skillDir = path.join(workspacePath, 'skills', code);
232
-
152
+
153
+ const skillDir = path.join(workspacePath, 'skills', code)
154
+
233
155
  if (fs.existsSync(skillDir)) {
234
- fs.rmSync(skillDir, { recursive: true, force: true });
156
+ fs.rmSync(skillDir, { recursive: true, force: true })
235
157
  sendEvent({ ...msgContent, status: 'ok' })
236
158
  } else {
237
159
  sendEvent({ ...msgContent, status: 'ok' })
238
160
  }
239
- }
161
+ }