@kevisual/cnb 0.0.28 → 0.0.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kevisual/cnb",
3
- "version": "0.0.28",
3
+ "version": "0.0.30",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -16,7 +16,7 @@
16
16
  ],
17
17
  "author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
18
18
  "license": "MIT",
19
- "packageManager": "pnpm@10.30.1",
19
+ "packageManager": "pnpm@10.30.2",
20
20
  "type": "module",
21
21
  "devDependencies": {
22
22
  "@kevisual/ai": "^0.0.24",
@@ -24,7 +24,7 @@
24
24
  "@kevisual/dts": "^0.0.4",
25
25
  "@kevisual/context": "^0.0.8",
26
26
  "@kevisual/types": "^0.0.12",
27
- "@opencode-ai/plugin": "^1.2.10",
27
+ "@opencode-ai/plugin": "^1.2.12",
28
28
  "@types/bun": "^1.3.9",
29
29
  "@types/node": "^25.3.0",
30
30
  "@types/ws": "^8.18.1",
@@ -38,8 +38,8 @@
38
38
  "zod": "^4.3.6"
39
39
  },
40
40
  "dependencies": {
41
- "@kevisual/query": "^0.0.49",
42
- "@kevisual/router": "^0.0.80",
41
+ "@kevisual/query": "^0.0.52",
42
+ "@kevisual/router": "^0.0.84",
43
43
  "@kevisual/use-config": "^1.0.30",
44
44
  "es-toolkit": "^1.44.0",
45
45
  "nanoid": "^5.1.6",
@@ -52,6 +52,7 @@
52
52
  "./opencode": "./dist/opencode.js",
53
53
  "./keep": "./dist/keep.js",
54
54
  "./keep.ts": "./src/keep.ts",
55
+ "./keep-file-live.ts": "./src/workspace/keep-file-live.ts",
55
56
  "./routes": "./dist/routes.js",
56
57
  "./src/*": "./src/*",
57
58
  "./agent/*": "./agent/*"
package/src/cnb-core.ts CHANGED
@@ -79,7 +79,7 @@ export class CNBCore {
79
79
  }
80
80
  delete _headers.Authorization;
81
81
  }
82
- console.log('Request URL:', url, data, _headers);
82
+ // console.log('Request URL:', url, data, _headers);
83
83
  const response = await fetch(url || '', {
84
84
  method,
85
85
  headers: _headers,
package/src/user/index.ts CHANGED
@@ -16,6 +16,18 @@ export class User extends CNBCore {
16
16
  useCookie: true,
17
17
  });
18
18
  }
19
+ /**
20
+ * 判断当前 Cookie 是否有效
21
+ * @returns
22
+ */
23
+ async checkCookieValid(): Promise<Result> {
24
+ const user = await this.getCurrentUser();
25
+ if (user.code === 200) {
26
+ return { code: 200, message: 'cookie valid' };
27
+ } else {
28
+ return { code: 401, message: 'cookie invalid' };
29
+ }
30
+ }
19
31
  /**
20
32
  * 使用 Token 获取用户信息
21
33
  * @returns
@@ -113,7 +113,62 @@ export class Workspace extends CNBCore {
113
113
 
114
114
  return this.post({ url: `/${repo}/-/workspace/start`, data });
115
115
  }
116
+ /**
117
+ * 添加使用cookie获取工作空间访问权限的功能,适用于需要保持工作空间连接状态的场景,
118
+ * 例如使用 WebSocket 连接工作空间时需要携带 cookie 进行身份验证。
119
+ * https://cnb.cool/kevisual/dev-env/-/workspace/vscode-web/cnb-708-1ji9sog7o-001
120
+ * @param repo
121
+ * @param pipelineId
122
+ * @returns
123
+ */
124
+ async getWorkspaceCookie(repo: string, pipelineId: string): Promise<Result<{ value: string, cookie: string; cookieName: string }>> {
125
+ const url = `${this.hackURL}/${repo}/-/workspace/vscode-web/${pipelineId}`;
126
+ const response = await fetch(url, {
127
+ method: 'GET',
128
+ redirect: 'manual',
129
+ headers: {
130
+ 'Cookie': this.cookie || '',
131
+ 'Accept': 'application/json',
132
+ }
133
+ });
134
+
135
+ // 第一次 302 重定向
136
+ if (response.status === 302 || response.status === 301) {
137
+ // 包含token的重定向 URL 通常在 Location 头中返回
138
+ // 类似 https://cnb-708-1ji9sog7o-001.cnb.space/login?t=orange:workspace:login-token:963691a2-35ce-4fef-a7ba-72723cefd226
139
+ const loginURL = response.headers.get('Location');
140
+ // 从 URL 参数中获取 cookieName,例如: orange:workspace:cookie-session:cnb-708-1ji9sog7o-001
141
+ const cookieName = `orange:workspace:cookie-session:${pipelineId}`;
142
+ // 第二次请求,也设置为 manual 防止自动重定向
143
+ const response2 = await fetch(loginURL || '', {
144
+ method: 'GET',
145
+ redirect: 'manual',
146
+ headers: {
147
+ 'Cookie': this.cookie || '',
148
+ 'Accept': 'application/json',
149
+ }
150
+ });
151
+
152
+ // 第二次 302 重定向,获取最终的 cookie 值
153
+ if (response2.status === 302 || response2.status === 301) {
154
+ // 从 Set-Cookie 头中获取 cookie 值
155
+ const setCookie = response2.headers.get('Set-Cookie');
156
+ // 解析 cookie 值
157
+ const cookieValue = setCookie?.split(';')[0]?.split('=')[1] || '';
116
158
 
159
+ return {
160
+ code: 200,
161
+ message: 'success',
162
+ data: { value: cookieValue, cookieName, cookie: `${cookieName}=${cookieValue}` }
163
+ };
164
+ }
165
+
166
+ // 如果不是重定向,尝试获取 JSON 数据
167
+ return { code: 500 };
168
+ }
169
+
170
+ return { code: 500, };
171
+ }
117
172
  }
118
173
  export interface WorkspaceLinkDetail {
119
174
  codebuddy: string;
@@ -0,0 +1,136 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import { execSync } from 'node:child_process';
5
+
6
+ export type KeepAliveData = {
7
+ wsUrl: string;
8
+ cookie: string;
9
+ repo: string;
10
+ pipelineId: string;
11
+ createdTime: number;
12
+ filePath: string;
13
+ pm2Name: string;
14
+ }
15
+
16
+ type KeepAliveCache = {
17
+ data: KeepAliveData[];
18
+ }
19
+
20
+ const keepAliveFilePath = path.join(os.homedir(), '.cnb/keepAliveCache.json');
21
+
22
+ export const runLive = (filePath: string, pm2Name: string) => {
23
+ // 使用 npx 运行命令
24
+ const cmdArgs = `cnb live -c ${filePath}`;
25
+
26
+ // 先停止已存在的同名 pm2 进程
27
+ const stopCmd = `pm2 delete ${pm2Name} 2>/dev/null || true`;
28
+ console.log('停止已存在的进程:', stopCmd);
29
+ try {
30
+ execSync(stopCmd, { stdio: 'inherit' });
31
+ } catch (error) {
32
+ console.log('停止进程失败或进程不存在:', error);
33
+ }
34
+
35
+ // 使用pm2启动
36
+ const pm2Cmd = `pm2 start ev --name ${pm2Name} --no-autorestart -- ${cmdArgs}`;
37
+ console.log('执行命令:', pm2Cmd);
38
+ try {
39
+ const result = execSync(pm2Cmd, { stdio: 'pipe', encoding: 'utf8' });
40
+ console.log(result);
41
+ } catch (error) {
42
+ console.error("状态码:", error.status);
43
+ console.error("错误详情:", error.stderr.toString()); // 这里会显示 ev 命令报的具体错误
44
+ }
45
+ }
46
+
47
+ export const stopLive = (pm2Name: string): boolean => {
48
+ const stopCmd = `pm2 delete ${pm2Name} 2>/dev/null || true`;
49
+ console.log('停止进程:', stopCmd);
50
+ try {
51
+ execSync(stopCmd, { stdio: 'inherit' });
52
+ console.log(`已停止 ${pm2Name} 的保持存活任务`);
53
+ return true;
54
+ } catch (error) {
55
+ console.error('停止进程失败:', error);
56
+ }
57
+ return false;
58
+ }
59
+
60
+ export function getKeepAliveCache(): KeepAliveCache {
61
+ try {
62
+ if (fs.existsSync(keepAliveFilePath)) {
63
+ const data = fs.readFileSync(keepAliveFilePath, 'utf-8');
64
+ const cache = JSON.parse(data) as KeepAliveCache;
65
+ return cache;
66
+ } else {
67
+ return { data: [] };
68
+ }
69
+ } catch (error) {
70
+ console.error('读取保持存活缓存文件失败:', error);
71
+ return { data: [] };
72
+ }
73
+ }
74
+
75
+ export function addKeepAliveData(data: KeepAliveData): KeepAliveCache {
76
+ const cache = getKeepAliveCache();
77
+ cache.data.push(data);
78
+ runLive(data.filePath, data.pm2Name);
79
+ try {
80
+ if (!fs.existsSync(path.dirname(keepAliveFilePath))) {
81
+ fs.mkdirSync(path.dirname(keepAliveFilePath), { recursive: true });
82
+ }
83
+ fs.writeFileSync(keepAliveFilePath, JSON.stringify(cache, null, 2), 'utf-8');
84
+ return cache;
85
+ } catch (error) {
86
+ console.error('写入保持存活缓存文件失败:', error);
87
+ return { data: [] };
88
+ }
89
+ }
90
+
91
+ export function removeKeepAliveData(repo: string, pipelineId: string): KeepAliveCache {
92
+ const cache = getKeepAliveCache();
93
+ const item = cache.data.find(item => item.repo === repo && item.pipelineId === pipelineId);
94
+ if (item) {
95
+ stopLive(item.pm2Name);
96
+ }
97
+ cache.data = cache.data.filter(item => item.repo !== repo || item.pipelineId !== pipelineId);
98
+ try {
99
+ fs.writeFileSync(keepAliveFilePath, JSON.stringify(cache, null, 2), 'utf-8');
100
+ return cache;
101
+ } catch (error) {
102
+ console.error('写入保持存活缓存文件失败:', error);
103
+ return { data: [] };
104
+ }
105
+ }
106
+
107
+ export const createLiveData = (data: { wsUrl: string, cookie: string, repo: string, pipelineId: string }): KeepAliveData => {
108
+ const { wsUrl, cookie, repo, pipelineId } = data;
109
+ const createdTime = Date.now();
110
+ const pm2Name = `${repo}__${pipelineId}`.replace(/\//g, '__');
111
+ const filePath = path.join(os.homedir(), '.cnb', `${pm2Name}.json`);
112
+ const _newData = { wss: wsUrl, wsUrl, cookie, repo, pipelineId, createdTime, filePath, pm2Name };
113
+ if (!fs.existsSync(path.dirname(filePath))) {
114
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
115
+ }
116
+ fs.writeFileSync(filePath, JSON.stringify(_newData, null, 2), 'utf-8');
117
+ return _newData;
118
+ }
119
+
120
+ export class KeepAliveManager {
121
+ static getCache() {
122
+ return getKeepAliveCache();
123
+ }
124
+
125
+ static add(data: KeepAliveData) {
126
+ return addKeepAliveData(data);
127
+ }
128
+
129
+ static createLiveData(data: { wsUrl: string, cookie: string, repo: string, pipelineId: string }): KeepAliveData {
130
+ return createLiveData(data);
131
+ }
132
+
133
+ static remove(repo: string, pipelineId: string) {
134
+ return removeKeepAliveData(repo, pipelineId);
135
+ }
136
+ }