@next-open-ai/openbot 0.2.8 → 0.3.2

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.
Files changed (64) hide show
  1. package/README.md +12 -1
  2. package/apps/desktop/renderer/dist/assets/index-DKtaRFW4.js +89 -0
  3. package/apps/desktop/renderer/dist/assets/index-QHuqXpWQ.css +10 -0
  4. package/apps/desktop/renderer/dist/index.html +2 -2
  5. package/dist/core/agent/agent-manager.d.ts +3 -0
  6. package/dist/core/agent/agent-manager.js +12 -6
  7. package/dist/core/config/desktop-config.d.ts +4 -0
  8. package/dist/core/config/desktop-config.js +5 -1
  9. package/dist/core/installer/index.d.ts +1 -1
  10. package/dist/core/installer/index.js +1 -1
  11. package/dist/core/installer/skill-installer.d.ts +9 -0
  12. package/dist/core/installer/skill-installer.js +94 -0
  13. package/dist/core/mcp/adapter.d.ts +17 -0
  14. package/dist/core/mcp/adapter.js +49 -0
  15. package/dist/core/mcp/client.d.ts +24 -0
  16. package/dist/core/mcp/client.js +70 -0
  17. package/dist/core/mcp/config.d.ts +22 -0
  18. package/dist/core/mcp/config.js +69 -0
  19. package/dist/core/mcp/index.d.ts +18 -0
  20. package/dist/core/mcp/index.js +20 -0
  21. package/dist/core/mcp/operator.d.ts +15 -0
  22. package/dist/core/mcp/operator.js +72 -0
  23. package/dist/core/mcp/transport/index.d.ts +11 -0
  24. package/dist/core/mcp/transport/index.js +16 -0
  25. package/dist/core/mcp/transport/sse.d.ts +20 -0
  26. package/dist/core/mcp/transport/sse.js +82 -0
  27. package/dist/core/mcp/transport/stdio.d.ts +32 -0
  28. package/dist/core/mcp/transport/stdio.js +132 -0
  29. package/dist/core/mcp/types.d.ts +72 -0
  30. package/dist/core/mcp/types.js +5 -0
  31. package/dist/core/tools/bookmark-tool.d.ts +9 -0
  32. package/dist/core/tools/bookmark-tool.js +118 -0
  33. package/dist/core/tools/index.d.ts +1 -0
  34. package/dist/core/tools/index.js +1 -0
  35. package/dist/gateway/methods/agent-chat.js +1 -0
  36. package/dist/gateway/methods/install-skill-from-upload.d.ts +14 -0
  37. package/dist/gateway/methods/install-skill-from-upload.js +13 -0
  38. package/dist/gateway/methods/run-scheduled-task.js +1 -0
  39. package/dist/gateway/server.js +24 -0
  40. package/dist/server/agent-config/agent-config.controller.d.ts +1 -1
  41. package/dist/server/agent-config/agent-config.service.d.ts +4 -1
  42. package/dist/server/agent-config/agent-config.service.js +2 -0
  43. package/dist/server/agents/agents.controller.js +3 -5
  44. package/dist/server/agents/agents.service.d.ts +1 -1
  45. package/dist/server/agents/agents.service.js +4 -2
  46. package/dist/server/app.module.js +2 -0
  47. package/dist/server/database/database.service.d.ts +7 -0
  48. package/dist/server/database/database.service.js +54 -5
  49. package/dist/server/saved-items/saved-items.controller.d.ts +26 -0
  50. package/dist/server/saved-items/saved-items.controller.js +78 -0
  51. package/dist/server/saved-items/saved-items.module.d.ts +2 -0
  52. package/dist/server/saved-items/saved-items.module.js +23 -0
  53. package/dist/server/saved-items/saved-items.service.d.ts +31 -0
  54. package/dist/server/saved-items/saved-items.service.js +105 -0
  55. package/dist/server/saved-items/tags.controller.d.ts +30 -0
  56. package/dist/server/saved-items/tags.controller.js +85 -0
  57. package/dist/server/saved-items/tags.service.d.ts +24 -0
  58. package/dist/server/saved-items/tags.service.js +84 -0
  59. package/dist/server/skills/skills.service.d.ts +2 -0
  60. package/dist/server/skills/skills.service.js +80 -16
  61. package/package.json +5 -1
  62. package/skills/url-bookmark/SKILL.md +36 -0
  63. package/apps/desktop/renderer/dist/assets/index-BOS-F8a4.js +0 -89
  64. package/apps/desktop/renderer/dist/assets/index-DxqxayUL.css +0 -10
@@ -0,0 +1,23 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { Module } from '@nestjs/common';
8
+ import { DatabaseModule } from '../database/database.module.js';
9
+ import { TagsService } from './tags.service.js';
10
+ import { TagsController } from './tags.controller.js';
11
+ import { SavedItemsService } from './saved-items.service.js';
12
+ import { SavedItemsController } from './saved-items.controller.js';
13
+ let SavedItemsModule = class SavedItemsModule {
14
+ };
15
+ SavedItemsModule = __decorate([
16
+ Module({
17
+ imports: [DatabaseModule],
18
+ controllers: [TagsController, SavedItemsController],
19
+ providers: [TagsService, SavedItemsService],
20
+ exports: [TagsService, SavedItemsService],
21
+ })
22
+ ], SavedItemsModule);
23
+ export { SavedItemsModule };
@@ -0,0 +1,31 @@
1
+ import { DatabaseService } from '../database/database.service.js';
2
+ import { TagsService } from './tags.service.js';
3
+ export interface SavedItem {
4
+ id: string;
5
+ url: string;
6
+ title: string | null;
7
+ workspace: string;
8
+ createdAt: number;
9
+ tagIds: string[];
10
+ tagNames?: string[];
11
+ }
12
+ export declare class SavedItemsService {
13
+ private readonly db;
14
+ private readonly tagsService;
15
+ constructor(db: DatabaseService, tagsService: TagsService);
16
+ private getTagIdsForItem;
17
+ private rowToSavedItem;
18
+ create(dto: {
19
+ url: string;
20
+ title?: string;
21
+ workspace?: string;
22
+ tagNames?: string[];
23
+ tagIds?: string[];
24
+ }): Promise<SavedItem>;
25
+ findAll(options?: {
26
+ tagId?: string;
27
+ workspace?: string;
28
+ }): Promise<SavedItem[]>;
29
+ findById(id: string): Promise<SavedItem | null>;
30
+ delete(id: string): Promise<void>;
31
+ }
@@ -0,0 +1,105 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { Injectable, NotFoundException } from '@nestjs/common';
11
+ import { DatabaseService } from '../database/database.service.js';
12
+ import { TagsService } from './tags.service.js';
13
+ import { randomUUID } from 'crypto';
14
+ let SavedItemsService = class SavedItemsService {
15
+ db;
16
+ tagsService;
17
+ constructor(db, tagsService) {
18
+ this.db = db;
19
+ this.tagsService = tagsService;
20
+ }
21
+ async getTagIdsForItem(savedItemId) {
22
+ const rows = this.db.all('SELECT tag_id FROM saved_item_tags WHERE saved_item_id = ?', [savedItemId]);
23
+ return rows.map((r) => r.tag_id);
24
+ }
25
+ async rowToSavedItem(row) {
26
+ const tagIds = await this.getTagIdsForItem(row.id);
27
+ const tagNames = [];
28
+ for (const tagId of tagIds) {
29
+ const tag = await this.tagsService.findById(tagId);
30
+ if (tag)
31
+ tagNames.push(tag.name);
32
+ }
33
+ return {
34
+ id: row.id,
35
+ url: row.url,
36
+ title: row.title,
37
+ workspace: row.workspace,
38
+ createdAt: row.created_at,
39
+ tagIds,
40
+ tagNames,
41
+ };
42
+ }
43
+ async create(dto) {
44
+ const url = (dto.url ?? '').trim();
45
+ if (!url)
46
+ throw new Error('url is required');
47
+ const workspace = (dto.workspace ?? 'default').trim() || 'default';
48
+ const title = dto.title?.trim() ?? null;
49
+ const id = randomUUID();
50
+ const createdAt = Date.now();
51
+ this.db.run('INSERT INTO saved_items (id, url, title, workspace, created_at) VALUES (?, ?, ?, ?, ?)', [id, url, title, workspace, createdAt]);
52
+ let tagIds = [];
53
+ if (dto.tagIds?.length) {
54
+ tagIds = dto.tagIds;
55
+ }
56
+ else if (dto.tagNames?.length) {
57
+ for (const name of dto.tagNames) {
58
+ const tag = await this.tagsService.findByName(name.trim());
59
+ if (tag)
60
+ tagIds.push(tag.id);
61
+ }
62
+ }
63
+ for (const tagId of tagIds) {
64
+ this.db.run('INSERT INTO saved_item_tags (saved_item_id, tag_id) VALUES (?, ?)', [id, tagId]);
65
+ }
66
+ const row = { id, url, title, workspace, created_at: createdAt };
67
+ return this.rowToSavedItem(row);
68
+ }
69
+ async findAll(options) {
70
+ let sql = 'SELECT id, url, title, workspace, created_at FROM saved_items WHERE 1=1';
71
+ const params = [];
72
+ if (options?.workspace) {
73
+ sql += ' AND workspace = ?';
74
+ params.push(options.workspace);
75
+ }
76
+ if (options?.tagId) {
77
+ sql += ' AND id IN (SELECT saved_item_id FROM saved_item_tags WHERE tag_id = ?)';
78
+ params.push(options.tagId);
79
+ }
80
+ sql += ' ORDER BY created_at DESC';
81
+ const rows = this.db.all(sql, params);
82
+ const result = [];
83
+ for (const row of rows) {
84
+ result.push(await this.rowToSavedItem(row));
85
+ }
86
+ return result;
87
+ }
88
+ async findById(id) {
89
+ const row = this.db.get('SELECT id, url, title, workspace, created_at FROM saved_items WHERE id = ?', [id]);
90
+ return row ? this.rowToSavedItem(row) : null;
91
+ }
92
+ async delete(id) {
93
+ const item = await this.findById(id);
94
+ if (!item)
95
+ throw new NotFoundException('收藏不存在');
96
+ this.db.run('DELETE FROM saved_item_tags WHERE saved_item_id = ?', [id]);
97
+ this.db.run('DELETE FROM saved_items WHERE id = ?', [id]);
98
+ }
99
+ };
100
+ SavedItemsService = __decorate([
101
+ Injectable(),
102
+ __metadata("design:paramtypes", [DatabaseService,
103
+ TagsService])
104
+ ], SavedItemsService);
105
+ export { SavedItemsService };
@@ -0,0 +1,30 @@
1
+ import { TagsService } from './tags.service.js';
2
+ export declare class TagsController {
3
+ private readonly tagsService;
4
+ constructor(tagsService: TagsService);
5
+ list(): Promise<{
6
+ success: boolean;
7
+ data: import("./tags.service.js").Tag[];
8
+ }>;
9
+ get(id: string): Promise<{
10
+ success: boolean;
11
+ data: import("./tags.service.js").Tag;
12
+ }>;
13
+ create(body: {
14
+ name: string;
15
+ sortOrder?: number;
16
+ }): Promise<{
17
+ success: boolean;
18
+ data: import("./tags.service.js").Tag;
19
+ }>;
20
+ update(id: string, body: {
21
+ name?: string;
22
+ sortOrder?: number;
23
+ }): Promise<{
24
+ success: boolean;
25
+ data: import("./tags.service.js").Tag;
26
+ }>;
27
+ delete(id: string): Promise<{
28
+ success: boolean;
29
+ }>;
30
+ }
@@ -0,0 +1,85 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
11
+ return function (target, key) { decorator(target, key, paramIndex); }
12
+ };
13
+ import { Controller, Get, Post, Put, Delete, Body, Param, HttpException, HttpStatus } from '@nestjs/common';
14
+ import { TagsService } from './tags.service.js';
15
+ let TagsController = class TagsController {
16
+ tagsService;
17
+ constructor(tagsService) {
18
+ this.tagsService = tagsService;
19
+ }
20
+ async list() {
21
+ const data = await this.tagsService.findAll();
22
+ return { success: true, data };
23
+ }
24
+ async get(id) {
25
+ const data = await this.tagsService.findById(id);
26
+ if (!data)
27
+ throw new HttpException('Tag not found', HttpStatus.NOT_FOUND);
28
+ return { success: true, data };
29
+ }
30
+ async create(body) {
31
+ const data = await this.tagsService.create({
32
+ name: body.name,
33
+ sortOrder: body.sortOrder,
34
+ });
35
+ return { success: true, data };
36
+ }
37
+ async update(id, body) {
38
+ const data = await this.tagsService.update(id, body);
39
+ return { success: true, data };
40
+ }
41
+ async delete(id) {
42
+ await this.tagsService.delete(id);
43
+ return { success: true };
44
+ }
45
+ };
46
+ __decorate([
47
+ Get(),
48
+ __metadata("design:type", Function),
49
+ __metadata("design:paramtypes", []),
50
+ __metadata("design:returntype", Promise)
51
+ ], TagsController.prototype, "list", null);
52
+ __decorate([
53
+ Get(':id'),
54
+ __param(0, Param('id')),
55
+ __metadata("design:type", Function),
56
+ __metadata("design:paramtypes", [String]),
57
+ __metadata("design:returntype", Promise)
58
+ ], TagsController.prototype, "get", null);
59
+ __decorate([
60
+ Post(),
61
+ __param(0, Body()),
62
+ __metadata("design:type", Function),
63
+ __metadata("design:paramtypes", [Object]),
64
+ __metadata("design:returntype", Promise)
65
+ ], TagsController.prototype, "create", null);
66
+ __decorate([
67
+ Put(':id'),
68
+ __param(0, Param('id')),
69
+ __param(1, Body()),
70
+ __metadata("design:type", Function),
71
+ __metadata("design:paramtypes", [String, Object]),
72
+ __metadata("design:returntype", Promise)
73
+ ], TagsController.prototype, "update", null);
74
+ __decorate([
75
+ Delete(':id'),
76
+ __param(0, Param('id')),
77
+ __metadata("design:type", Function),
78
+ __metadata("design:paramtypes", [String]),
79
+ __metadata("design:returntype", Promise)
80
+ ], TagsController.prototype, "delete", null);
81
+ TagsController = __decorate([
82
+ Controller('tags'),
83
+ __metadata("design:paramtypes", [TagsService])
84
+ ], TagsController);
85
+ export { TagsController };
@@ -0,0 +1,24 @@
1
+ import { DatabaseService } from '../database/database.service.js';
2
+ export interface Tag {
3
+ id: string;
4
+ name: string;
5
+ sortOrder: number;
6
+ createdAt: number;
7
+ }
8
+ export declare class TagsService {
9
+ private readonly db;
10
+ constructor(db: DatabaseService);
11
+ private rowToTag;
12
+ findAll(): Promise<Tag[]>;
13
+ findById(id: string): Promise<Tag | null>;
14
+ findByName(name: string): Promise<Tag | null>;
15
+ create(dto: {
16
+ name: string;
17
+ sortOrder?: number;
18
+ }): Promise<Tag>;
19
+ update(id: string, dto: {
20
+ name?: string;
21
+ sortOrder?: number;
22
+ }): Promise<Tag>;
23
+ delete(id: string): Promise<void>;
24
+ }
@@ -0,0 +1,84 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { Injectable, ConflictException, NotFoundException } from '@nestjs/common';
11
+ import { DatabaseService } from '../database/database.service.js';
12
+ import { randomUUID } from 'crypto';
13
+ let TagsService = class TagsService {
14
+ db;
15
+ constructor(db) {
16
+ this.db = db;
17
+ }
18
+ rowToTag(row) {
19
+ return {
20
+ id: row.id,
21
+ name: row.name,
22
+ sortOrder: row.sort_order,
23
+ createdAt: row.created_at,
24
+ };
25
+ }
26
+ async findAll() {
27
+ const rows = this.db.all('SELECT id, name, sort_order, created_at FROM tags ORDER BY sort_order ASC, name ASC', []);
28
+ return rows.map((r) => this.rowToTag(r));
29
+ }
30
+ async findById(id) {
31
+ const row = this.db.get('SELECT id, name, sort_order, created_at FROM tags WHERE id = ?', [id]);
32
+ return row ? this.rowToTag(row) : null;
33
+ }
34
+ async findByName(name) {
35
+ const row = this.db.get('SELECT id, name, sort_order, created_at FROM tags WHERE name = ?', [
36
+ name.trim(),
37
+ ]);
38
+ return row ? this.rowToTag(row) : null;
39
+ }
40
+ async create(dto) {
41
+ const name = (dto.name ?? '').trim();
42
+ if (!name)
43
+ throw new ConflictException('标签名不能为空');
44
+ const existing = await this.findByName(name);
45
+ if (existing)
46
+ throw new ConflictException('该标签名已存在');
47
+ const id = randomUUID();
48
+ const sortOrder = dto.sortOrder ?? 0;
49
+ const createdAt = Date.now();
50
+ this.db.run('INSERT INTO tags (id, name, sort_order, created_at) VALUES (?, ?, ?, ?)', [id, name, sortOrder, createdAt]);
51
+ return { id, name, sortOrder, createdAt };
52
+ }
53
+ async update(id, dto) {
54
+ const tag = await this.findById(id);
55
+ if (!tag)
56
+ throw new NotFoundException('标签不存在');
57
+ if (dto.name !== undefined) {
58
+ const name = dto.name.trim();
59
+ if (!name)
60
+ throw new ConflictException('标签名不能为空');
61
+ const existing = await this.findByName(name);
62
+ if (existing && existing.id !== id)
63
+ throw new ConflictException('该标签名已存在');
64
+ this.db.run('UPDATE tags SET name = ? WHERE id = ?', [name, id]);
65
+ tag.name = name;
66
+ }
67
+ if (dto.sortOrder !== undefined) {
68
+ this.db.run('UPDATE tags SET sort_order = ? WHERE id = ?', [dto.sortOrder, id]);
69
+ tag.sortOrder = dto.sortOrder;
70
+ }
71
+ return tag;
72
+ }
73
+ async delete(id) {
74
+ const tag = await this.findById(id);
75
+ if (!tag)
76
+ throw new NotFoundException('标签不存在');
77
+ this.db.run('DELETE FROM tags WHERE id = ?', [id]);
78
+ }
79
+ };
80
+ TagsService = __decorate([
81
+ Injectable(),
82
+ __metadata("design:paramtypes", [DatabaseService])
83
+ ], TagsService);
84
+ export { TagsService };
@@ -51,6 +51,8 @@ export declare class SkillsService {
51
51
  getSkillContent(name: string): Promise<string | null>;
52
52
  /** 仅返回指定工作区下的技能(文件目录管理,不涉及 SQLite) */
53
53
  getSkillsForWorkspace(workspaceName: string): Promise<Skill[]>;
54
+ /** 从 skill.json 或 package.json 解析出 Skill(兼容无 SKILL.md 的 npm 风格技能目录) */
55
+ private parseSkillJson;
54
56
  getSkillContentForWorkspace(workspaceName: string, name: string): Promise<string | null>;
55
57
  /** 在工作区下新增技能(创建目录 + SKILL.md) */
56
58
  addSkill(workspaceName: string, name: string, options?: {
@@ -20,6 +20,24 @@ import { getOpenbotAgentDir, getOpenbotWorkspaceDir } from '../../core/agent/age
20
20
  const execAsync = promisify(exec);
21
21
  /** 工作区技能名仅允许英文、数字、下划线、连字符 */
22
22
  const SKILL_NAME_REGEX = /^[a-zA-Z0-9_-]+$/;
23
+ /**
24
+ * 系统技能目录:仅两条规则,不做复杂搜索。
25
+ * - 运行时/打包:由桌面 main 或部署方设置 OPENBOT_SYSTEM_SKILLS_DIR,用该目录。
26
+ * - 开发/测试:未设置时仅尝试「当前工作目录下的 skills」(从项目根启动时即项目目录下的 skills)。
27
+ */
28
+ function resolveSystemSkillsDir() {
29
+ const envDir = process.env.OPENBOT_SYSTEM_SKILLS_DIR?.trim();
30
+ if (envDir) {
31
+ const abs = resolve(envDir);
32
+ if (existsSync(abs))
33
+ return abs;
34
+ return null;
35
+ }
36
+ const cwdSkills = resolve(process.cwd(), 'skills');
37
+ if (existsSync(cwdSkills))
38
+ return cwdSkills;
39
+ return null;
40
+ }
23
41
  /** 技能目录与来源:全局=OPENBOT_AGENT_DIR/skills,系统=项目根/skills,工作区=OPENBOT_WORKSPACE_DIR/xxx/skills */
24
42
  let SkillsService = class SkillsService {
25
43
  /** 待扫描的目录列表 */
@@ -29,14 +47,14 @@ let SkillsService = class SkillsService {
29
47
  const workspaceName = process.env.OPENBOT_WORKSPACE || 'default';
30
48
  // 全局技能:OPENBOT_AGENT_DIR/skills(默认 ~/.openbot/agent/skills)
31
49
  const globalSkillsDir = join(getOpenbotAgentDir(), 'skills');
32
- // 系统技能:当前项目代码根目录下的 skills
33
- const systemSkillsDir = resolve(cwd, 'skills');
50
+ // 系统技能:OPENBOT_SYSTEM_SKILLS_DIR 或 cwd/skills(仅此两种)
51
+ const systemSkillsDir = resolveSystemSkillsDir();
34
52
  // 工作区技能:OPENBOT_WORKSPACE_DIR/<name>/skills(默认 ~/.openbot/workspace/xxx/skills)
35
53
  const workspaceSkillsDir = join(getOpenbotWorkspaceDir(), workspaceName, 'skills');
36
54
  if (existsSync(globalSkillsDir)) {
37
55
  this.skillPaths.push({ path: globalSkillsDir, source: 'global' });
38
56
  }
39
- if (existsSync(systemSkillsDir)) {
57
+ if (systemSkillsDir) {
40
58
  this.skillPaths.push({ path: systemSkillsDir, source: 'system' });
41
59
  }
42
60
  if (existsSync(workspaceSkillsDir)) {
@@ -220,8 +238,20 @@ let SkillsService = class SkillsService {
220
238
  /** 仅返回指定工作区下的技能(文件目录管理,不涉及 SQLite) */
221
239
  async getSkillsForWorkspace(workspaceName) {
222
240
  const skillPath = this.getWorkspaceSkillsDir(workspaceName);
223
- if (!existsSync(skillPath))
224
- return [];
241
+ if (!existsSync(skillPath)) {
242
+ if (workspaceName === DEFAULT_AGENT_ID || workspaceName === 'default') {
243
+ try {
244
+ await mkdir(skillPath, { recursive: true });
245
+ }
246
+ catch (e) {
247
+ console.warn(`[skills] 创建 default 工作区技能目录失败: ${skillPath}`, e);
248
+ }
249
+ }
250
+ if (!existsSync(skillPath)) {
251
+ console.warn(`[skills] 工作区技能目录不存在,返回空列表: workspace=${workspaceName}, path=${skillPath}`);
252
+ return [];
253
+ }
254
+ }
225
255
  const skills = [];
226
256
  try {
227
257
  const entries = await readdir(skillPath);
@@ -232,13 +262,24 @@ let SkillsService = class SkillsService {
232
262
  if (!stats.isDirectory())
233
263
  continue;
234
264
  const skillMdPath = join(fullPath, 'SKILL.md');
235
- if (!existsSync(skillMdPath))
236
- continue;
237
- const content = await readFile(skillMdPath, 'utf-8');
238
- skills.push(this.parseSkillFile(entry, content, fullPath, 'workspace'));
265
+ const skillJsonPath = join(fullPath, 'skill.json');
266
+ const packageJsonPath = join(fullPath, 'package.json');
267
+ if (existsSync(skillMdPath)) {
268
+ const content = await readFile(skillMdPath, 'utf-8');
269
+ skills.push(this.parseSkillFile(entry, content, fullPath, 'workspace'));
270
+ }
271
+ else if (existsSync(skillJsonPath)) {
272
+ const skill = await this.parseSkillJson(entry, skillJsonPath, fullPath);
273
+ if (skill)
274
+ skills.push(skill);
275
+ }
276
+ else if (existsSync(packageJsonPath)) {
277
+ const skill = await this.parseSkillJson(entry, packageJsonPath, fullPath);
278
+ if (skill)
279
+ skills.push(skill);
280
+ }
239
281
  }
240
282
  catch (entryError) {
241
- // 跳过无效条目:ENOENT(目录已删/损坏的符号链接)、无权限等
242
283
  if (entryError?.code !== 'ENOENT') {
243
284
  console.warn(`Skipping workspace skill entry ${entry}:`, entryError?.message ?? entryError);
244
285
  }
@@ -250,18 +291,41 @@ let SkillsService = class SkillsService {
250
291
  }
251
292
  return skills;
252
293
  }
294
+ /** 从 skill.json 或 package.json 解析出 Skill(兼容无 SKILL.md 的 npm 风格技能目录) */
295
+ async parseSkillJson(entryName, jsonPath, fullPath) {
296
+ try {
297
+ const content = await readFile(jsonPath, 'utf-8');
298
+ const data = JSON.parse(content);
299
+ const name = (data.name || entryName).trim() || entryName;
300
+ const description = (data.description || '').trim() || 'No description available';
301
+ return { name: entryName, description, path: fullPath, source: 'workspace' };
302
+ }
303
+ catch {
304
+ return null;
305
+ }
306
+ }
253
307
  async getSkillContentForWorkspace(workspaceName, name) {
254
308
  const skillPath = this.getWorkspaceSkillsDir(workspaceName);
255
309
  const fullPath = join(skillPath, name);
256
310
  const skillMdPath = join(fullPath, 'SKILL.md');
257
- if (!existsSync(skillMdPath))
258
- return null;
259
- try {
260
- return await readFile(skillMdPath, 'utf-8');
311
+ const readmePath = join(fullPath, 'README.md');
312
+ if (existsSync(skillMdPath)) {
313
+ try {
314
+ return await readFile(skillMdPath, 'utf-8');
315
+ }
316
+ catch {
317
+ return null;
318
+ }
261
319
  }
262
- catch {
263
- return null;
320
+ if (existsSync(readmePath)) {
321
+ try {
322
+ return await readFile(readmePath, 'utf-8');
323
+ }
324
+ catch {
325
+ return null;
326
+ }
264
327
  }
328
+ return null;
265
329
  }
266
330
  /** 在工作区下新增技能(创建目录 + SKILL.md) */
267
331
  async addSkill(workspaceName, name, options) {
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.2.8",
6
+ "version": "0.3.2",
7
7
  "description": "CLI and library to run prompts with skill paths (Agent Skills style). Use as npm package or openbot CLI.",
8
8
  "type": "module",
9
9
  "main": "dist/index.js",
@@ -33,9 +33,11 @@
33
33
  "@nestjs/platform-socket.io": "^10.3.0",
34
34
  "@nestjs/websockets": "^10.3.0",
35
35
  "@sinclair/typebox": "^0.34.41",
36
+ "adm-zip": "^0.5.16",
36
37
  "agent-browser": "^0.8.5",
37
38
  "commander": "^14.0.2",
38
39
  "croner": "^9.1.0",
40
+ "multer": "^1.4.5-lts.1",
39
41
  "reflect-metadata": "^0.2.1",
40
42
  "rxjs": "^7.8.1",
41
43
  "sql.js": "^1.13.0",
@@ -44,8 +46,10 @@
44
46
  },
45
47
  "devDependencies": {
46
48
  "@nestjs/testing": "^10.3.0",
49
+ "@types/adm-zip": "^0.5.7",
47
50
  "@types/express": "^4.17.21",
48
51
  "@types/jest": "^29.5.14",
52
+ "@types/multer": "^2.0.0",
49
53
  "@types/node": "^22.0.0",
50
54
  "@types/sql.js": "^1.4.9",
51
55
  "@types/ws": "^8.18.1",
@@ -0,0 +1,36 @@
1
+ ---
2
+ name: url-bookmark
3
+ description: 根据用户指令将 URL 保存为收藏,并匹配系统中已维护的标签。当用户说「收藏这个链接」「把这条存到技术标签」「保存网址」等时使用。先调用 get_bookmark_tags 获取可用标签列表,再根据用户意图选择匹配的标签,最后用 save_bookmark 保存。
4
+ allowed-tools: get_bookmark_tags, save_bookmark
5
+ ---
6
+
7
+ # URL 收藏技能
8
+
9
+ ## 何时使用
10
+
11
+ 当用户希望**保存一个链接/URL 到收藏**时使用本技能,例如:
12
+
13
+ - 「收藏这个链接」「把这条链接存一下」
14
+ - 「把这个网址加到技术标签」「存到待读里」
15
+ - 「保存当前页面」「记一下这个地址」
16
+
17
+ ## 工作流程
18
+
19
+ 1. **获取可用标签**:先调用工具 `get_bookmark_tags`,获取系统中已维护的标签列表(用户在「设置 → 标签管理」中维护)。
20
+ 2. **理解用户意图**:从用户话里提取:
21
+ - 要收藏的 **URL**(用户可能直接给出,或说「当前页面」「刚发的那条」等,需结合上下文确定);
22
+ - 用户指定的**标签**(如「技术」「待读」),须与 `get_bookmark_tags` 返回的标签名**完全一致**;若用户未指定,可根据内容推断一个已有标签或留空。
23
+ 3. **保存收藏**:调用工具 `save_bookmark`,传入:
24
+ - `url`:必填,要收藏的链接;
25
+ - `title`:可选,可取自页面标题或用户描述;
26
+ - `tagNames`:可选,从可用标签中选出的名称数组(须与系统标签一致)。
27
+
28
+ ## 工具说明
29
+
30
+ - **get_bookmark_tags**:无参数。返回当前可用的标签名称列表,供后续匹配。
31
+ - **save_bookmark**:参数 `url`(必填)、`title`(可选)、`tagNames`(可选,字符串数组)。标签名必须来自 get_bookmark_tags,否则不会关联成功。
32
+
33
+ ## 注意
34
+
35
+ - 标签由用户在「设置 → 标签管理」中维护;若尚无标签,可只保存 URL,不传 `tagNames`。
36
+ - 用户说「存到某某分类」时,将「某某」与 get_bookmark_tags 返回的标签名做匹配(同义或相近即可选对应标签)。