@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.
- package/README.md +12 -1
- package/apps/desktop/renderer/dist/assets/index-DKtaRFW4.js +89 -0
- package/apps/desktop/renderer/dist/assets/index-QHuqXpWQ.css +10 -0
- package/apps/desktop/renderer/dist/index.html +2 -2
- package/dist/core/agent/agent-manager.d.ts +3 -0
- package/dist/core/agent/agent-manager.js +12 -6
- package/dist/core/config/desktop-config.d.ts +4 -0
- package/dist/core/config/desktop-config.js +5 -1
- package/dist/core/installer/index.d.ts +1 -1
- package/dist/core/installer/index.js +1 -1
- package/dist/core/installer/skill-installer.d.ts +9 -0
- package/dist/core/installer/skill-installer.js +94 -0
- package/dist/core/mcp/adapter.d.ts +17 -0
- package/dist/core/mcp/adapter.js +49 -0
- package/dist/core/mcp/client.d.ts +24 -0
- package/dist/core/mcp/client.js +70 -0
- package/dist/core/mcp/config.d.ts +22 -0
- package/dist/core/mcp/config.js +69 -0
- package/dist/core/mcp/index.d.ts +18 -0
- package/dist/core/mcp/index.js +20 -0
- package/dist/core/mcp/operator.d.ts +15 -0
- package/dist/core/mcp/operator.js +72 -0
- package/dist/core/mcp/transport/index.d.ts +11 -0
- package/dist/core/mcp/transport/index.js +16 -0
- package/dist/core/mcp/transport/sse.d.ts +20 -0
- package/dist/core/mcp/transport/sse.js +82 -0
- package/dist/core/mcp/transport/stdio.d.ts +32 -0
- package/dist/core/mcp/transport/stdio.js +132 -0
- package/dist/core/mcp/types.d.ts +72 -0
- package/dist/core/mcp/types.js +5 -0
- package/dist/core/tools/bookmark-tool.d.ts +9 -0
- package/dist/core/tools/bookmark-tool.js +118 -0
- package/dist/core/tools/index.d.ts +1 -0
- package/dist/core/tools/index.js +1 -0
- package/dist/gateway/methods/agent-chat.js +1 -0
- package/dist/gateway/methods/install-skill-from-upload.d.ts +14 -0
- package/dist/gateway/methods/install-skill-from-upload.js +13 -0
- package/dist/gateway/methods/run-scheduled-task.js +1 -0
- package/dist/gateway/server.js +24 -0
- package/dist/server/agent-config/agent-config.controller.d.ts +1 -1
- package/dist/server/agent-config/agent-config.service.d.ts +4 -1
- package/dist/server/agent-config/agent-config.service.js +2 -0
- package/dist/server/agents/agents.controller.js +3 -5
- package/dist/server/agents/agents.service.d.ts +1 -1
- package/dist/server/agents/agents.service.js +4 -2
- package/dist/server/app.module.js +2 -0
- package/dist/server/database/database.service.d.ts +7 -0
- package/dist/server/database/database.service.js +54 -5
- package/dist/server/saved-items/saved-items.controller.d.ts +26 -0
- package/dist/server/saved-items/saved-items.controller.js +78 -0
- package/dist/server/saved-items/saved-items.module.d.ts +2 -0
- package/dist/server/saved-items/saved-items.module.js +23 -0
- package/dist/server/saved-items/saved-items.service.d.ts +31 -0
- package/dist/server/saved-items/saved-items.service.js +105 -0
- package/dist/server/saved-items/tags.controller.d.ts +30 -0
- package/dist/server/saved-items/tags.controller.js +85 -0
- package/dist/server/saved-items/tags.service.d.ts +24 -0
- package/dist/server/saved-items/tags.service.js +84 -0
- package/dist/server/skills/skills.service.d.ts +2 -0
- package/dist/server/skills/skills.service.js +80 -16
- package/package.json +5 -1
- package/skills/url-bookmark/SKILL.md +36 -0
- package/apps/desktop/renderer/dist/assets/index-BOS-F8a4.js +0 -89
- 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
|
-
//
|
|
33
|
-
const systemSkillsDir =
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
|
|
263
|
-
|
|
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
|
|
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 返回的标签名做匹配(同义或相近即可选对应标签)。
|