@team-semicolon/semo-cli 4.1.5 → 4.2.0
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 +3 -4
- package/dist/commands/audit.d.ts +27 -0
- package/dist/commands/audit.js +338 -0
- package/dist/commands/bots.js +524 -24
- package/dist/commands/context.d.ts +14 -3
- package/dist/commands/context.js +192 -113
- package/dist/commands/db.d.ts +9 -0
- package/dist/commands/db.js +189 -0
- package/dist/commands/get.d.ts +1 -2
- package/dist/commands/get.js +24 -116
- package/dist/commands/sessions.d.ts +2 -1
- package/dist/commands/sessions.js +31 -62
- package/dist/commands/skill-sync.d.ts +28 -0
- package/dist/commands/skill-sync.js +111 -0
- package/dist/commands/skill-sync.test.d.ts +16 -0
- package/dist/commands/skill-sync.test.js +186 -0
- package/dist/database.d.ts +41 -3
- package/dist/database.js +128 -554
- package/dist/env-parser.d.ts +5 -0
- package/dist/env-parser.js +27 -0
- package/dist/global-cache.d.ts +12 -0
- package/dist/global-cache.js +184 -0
- package/dist/index.js +352 -817
- package/dist/kb.d.ts +24 -39
- package/dist/kb.js +121 -175
- package/package.json +1 -1
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* skill-sync 테스트 — scanSkills 함수의 파일 스캔 로직 검증
|
|
4
|
+
*
|
|
5
|
+
* 실행: npx ts-node packages/cli/src/commands/skill-sync.test.ts
|
|
6
|
+
*
|
|
7
|
+
* 테스트 케이스:
|
|
8
|
+
* 1. 빈 디렉토리 → 0개
|
|
9
|
+
* 2. 봇 전용 스킬 → flat name으로 반환
|
|
10
|
+
* 3. 복수 봇의 전용 스킬 — botId 정확성
|
|
11
|
+
* 4. .skill 확장자 디렉토리 → 스킵
|
|
12
|
+
* 5. SKILL.md 없는 디렉토리 → 스킵
|
|
13
|
+
* 6. 스킬 변경 감지 — 파일 수정 후 재스캔
|
|
14
|
+
* 7. 스킬 추가 감지 — 새 디렉토리 추가 후 재스캔
|
|
15
|
+
* 8. 스킬 삭제 감지 — SKILL.md 삭제 후 재스캔
|
|
16
|
+
*/
|
|
17
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
20
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
21
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
22
|
+
}
|
|
23
|
+
Object.defineProperty(o, k2, desc);
|
|
24
|
+
}) : (function(o, m, k, k2) {
|
|
25
|
+
if (k2 === undefined) k2 = k;
|
|
26
|
+
o[k2] = m[k];
|
|
27
|
+
}));
|
|
28
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
29
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
30
|
+
}) : function(o, v) {
|
|
31
|
+
o["default"] = v;
|
|
32
|
+
});
|
|
33
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
34
|
+
var ownKeys = function(o) {
|
|
35
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
36
|
+
var ar = [];
|
|
37
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
38
|
+
return ar;
|
|
39
|
+
};
|
|
40
|
+
return ownKeys(o);
|
|
41
|
+
};
|
|
42
|
+
return function (mod) {
|
|
43
|
+
if (mod && mod.__esModule) return mod;
|
|
44
|
+
var result = {};
|
|
45
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
46
|
+
__setModuleDefault(result, mod);
|
|
47
|
+
return result;
|
|
48
|
+
};
|
|
49
|
+
})();
|
|
50
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
51
|
+
const fs = __importStar(require("fs"));
|
|
52
|
+
const path = __importStar(require("path"));
|
|
53
|
+
const os = __importStar(require("os"));
|
|
54
|
+
const skill_sync_1 = require("./skill-sync");
|
|
55
|
+
let tmpDir;
|
|
56
|
+
let passed = 0;
|
|
57
|
+
let failed = 0;
|
|
58
|
+
function setup() {
|
|
59
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "skill-sync-test-"));
|
|
60
|
+
fs.mkdirSync(path.join(dir, "bot-workspaces"), { recursive: true });
|
|
61
|
+
return dir;
|
|
62
|
+
}
|
|
63
|
+
function cleanup(dir) {
|
|
64
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
65
|
+
}
|
|
66
|
+
function assert(condition, message) {
|
|
67
|
+
if (condition) {
|
|
68
|
+
passed++;
|
|
69
|
+
console.log(` ✅ ${message}`);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
failed++;
|
|
73
|
+
console.log(` ❌ ${message}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function makeBotSkill(dir, botId, skillName, content) {
|
|
77
|
+
const skillDir = path.join(dir, "bot-workspaces", botId, "skills", skillName);
|
|
78
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
79
|
+
fs.writeFileSync(path.join(skillDir, "SKILL.md"), content);
|
|
80
|
+
}
|
|
81
|
+
// ─── 테스트 ───────────────────────────────────────────────
|
|
82
|
+
console.log("\n🧪 skill-sync.test.ts\n");
|
|
83
|
+
// 1. 빈 디렉토리
|
|
84
|
+
console.log("Case 1: 빈 디렉토리");
|
|
85
|
+
tmpDir = setup();
|
|
86
|
+
{
|
|
87
|
+
const skills = (0, skill_sync_1.scanSkills)(tmpDir);
|
|
88
|
+
assert(skills.length === 0, "skills = 0");
|
|
89
|
+
}
|
|
90
|
+
cleanup(tmpDir);
|
|
91
|
+
// 2. 봇 전용 스킬 — flat name
|
|
92
|
+
console.log("Case 2: 봇 전용 스킬 flat name");
|
|
93
|
+
tmpDir = setup();
|
|
94
|
+
{
|
|
95
|
+
makeBotSkill(tmpDir, "semiclaw", "github-issue-pipeline", "# GH Pipeline");
|
|
96
|
+
const skills = (0, skill_sync_1.scanSkills)(tmpDir);
|
|
97
|
+
assert(skills.length === 1, `skills = 1 (got ${skills.length})`);
|
|
98
|
+
assert(skills[0].name === "github-issue-pipeline", `name = github-issue-pipeline (got ${skills[0].name})`);
|
|
99
|
+
assert(skills[0].botId === "semiclaw", "botId = semiclaw");
|
|
100
|
+
assert(skills[0].package === "openclaw", "package = openclaw");
|
|
101
|
+
}
|
|
102
|
+
cleanup(tmpDir);
|
|
103
|
+
// 3. 복수 봇의 전용 스킬 — botId 정확성
|
|
104
|
+
console.log("Case 3: 복수 봇 botId 정확성");
|
|
105
|
+
tmpDir = setup();
|
|
106
|
+
{
|
|
107
|
+
makeBotSkill(tmpDir, "semiclaw", "skill-a", "# A");
|
|
108
|
+
makeBotSkill(tmpDir, "semiclaw", "skill-b", "# B");
|
|
109
|
+
makeBotSkill(tmpDir, "workclaw", "skill-c", "# C");
|
|
110
|
+
makeBotSkill(tmpDir, "infraclaw", "skill-d", "# D");
|
|
111
|
+
const skills = (0, skill_sync_1.scanSkills)(tmpDir);
|
|
112
|
+
assert(skills.length === 4, `총 4개 (got ${skills.length})`);
|
|
113
|
+
const semiclawSkills = skills.filter((s) => s.botId === "semiclaw");
|
|
114
|
+
const workclawSkills = skills.filter((s) => s.botId === "workclaw");
|
|
115
|
+
const infraclawSkills = skills.filter((s) => s.botId === "infraclaw");
|
|
116
|
+
assert(semiclawSkills.length === 2, `semiclaw: 2개 (got ${semiclawSkills.length})`);
|
|
117
|
+
assert(workclawSkills.length === 1, `workclaw: 1개 (got ${workclawSkills.length})`);
|
|
118
|
+
assert(infraclawSkills.length === 1, `infraclaw: 1개 (got ${infraclawSkills.length})`);
|
|
119
|
+
}
|
|
120
|
+
cleanup(tmpDir);
|
|
121
|
+
// 4. .skill 확장자 디렉토리 → 스킵
|
|
122
|
+
console.log("Case 4: .skill 확장자 디렉토리");
|
|
123
|
+
tmpDir = setup();
|
|
124
|
+
{
|
|
125
|
+
const dotSkillDir = path.join(tmpDir, "bot-workspaces", "semiclaw", "skills", "old-format.skill");
|
|
126
|
+
fs.mkdirSync(dotSkillDir, { recursive: true });
|
|
127
|
+
fs.writeFileSync(path.join(dotSkillDir, "SKILL.md"), "should be skipped");
|
|
128
|
+
makeBotSkill(tmpDir, "semiclaw", "valid-skill", "# Valid");
|
|
129
|
+
const skills = (0, skill_sync_1.scanSkills)(tmpDir);
|
|
130
|
+
assert(skills.length === 1, `.skill 디렉토리 스킵, skills = 1 (got ${skills.length})`);
|
|
131
|
+
assert(skills[0].name === "valid-skill", "올바른 스킬만 반환");
|
|
132
|
+
}
|
|
133
|
+
cleanup(tmpDir);
|
|
134
|
+
// 5. SKILL.md 없는 디렉토리 → 스킵
|
|
135
|
+
console.log("Case 5: SKILL.md 없는 디렉토리");
|
|
136
|
+
tmpDir = setup();
|
|
137
|
+
{
|
|
138
|
+
const emptyDir = path.join(tmpDir, "bot-workspaces", "semiclaw", "skills", "no-skill-md");
|
|
139
|
+
fs.mkdirSync(emptyDir, { recursive: true });
|
|
140
|
+
makeBotSkill(tmpDir, "semiclaw", "valid-skill", "content");
|
|
141
|
+
const skills = (0, skill_sync_1.scanSkills)(tmpDir);
|
|
142
|
+
assert(skills.length === 1, `SKILL.md 없는 디렉토리 스킵, skills = 1 (got ${skills.length})`);
|
|
143
|
+
}
|
|
144
|
+
cleanup(tmpDir);
|
|
145
|
+
// 6. 스킬 변경 감지 — 파일 수정 후 재스캔
|
|
146
|
+
console.log("Case 6: 스킬 변경 감지");
|
|
147
|
+
tmpDir = setup();
|
|
148
|
+
{
|
|
149
|
+
makeBotSkill(tmpDir, "semiclaw", "review", "# v1 content");
|
|
150
|
+
const scan1 = (0, skill_sync_1.scanSkills)(tmpDir);
|
|
151
|
+
assert(scan1[0].prompt === "# v1 content", "초기 스캔: v1");
|
|
152
|
+
fs.writeFileSync(path.join(tmpDir, "bot-workspaces", "semiclaw", "skills", "review", "SKILL.md"), "# v2 updated content");
|
|
153
|
+
const scan2 = (0, skill_sync_1.scanSkills)(tmpDir);
|
|
154
|
+
assert(scan2[0].prompt === "# v2 updated content", "재스캔: v2 반영됨");
|
|
155
|
+
}
|
|
156
|
+
cleanup(tmpDir);
|
|
157
|
+
// 7. 스킬 추가 감지 — 새 디렉토리 추가 후 재스캔
|
|
158
|
+
console.log("Case 7: 스킬 추가 감지");
|
|
159
|
+
tmpDir = setup();
|
|
160
|
+
{
|
|
161
|
+
makeBotSkill(tmpDir, "semiclaw", "existing", "# Existing");
|
|
162
|
+
const scan1 = (0, skill_sync_1.scanSkills)(tmpDir);
|
|
163
|
+
assert(scan1.length === 1, "초기: 1개");
|
|
164
|
+
makeBotSkill(tmpDir, "semiclaw", "new-skill", "# New Skill");
|
|
165
|
+
const scan2 = (0, skill_sync_1.scanSkills)(tmpDir);
|
|
166
|
+
assert(scan2.length === 2, `추가 후: 2개 (got ${scan2.length})`);
|
|
167
|
+
}
|
|
168
|
+
cleanup(tmpDir);
|
|
169
|
+
// 8. 스킬 삭제 감지 — SKILL.md 삭제 후 재스캔
|
|
170
|
+
console.log("Case 8: 스킬 삭제 감지");
|
|
171
|
+
tmpDir = setup();
|
|
172
|
+
{
|
|
173
|
+
makeBotSkill(tmpDir, "semiclaw", "to-delete", "# Will be deleted");
|
|
174
|
+
makeBotSkill(tmpDir, "semiclaw", "keep", "# Keep");
|
|
175
|
+
const scan1 = (0, skill_sync_1.scanSkills)(tmpDir);
|
|
176
|
+
assert(scan1.length === 2, "초기: 2개");
|
|
177
|
+
fs.unlinkSync(path.join(tmpDir, "bot-workspaces", "semiclaw", "skills", "to-delete", "SKILL.md"));
|
|
178
|
+
const scan2 = (0, skill_sync_1.scanSkills)(tmpDir);
|
|
179
|
+
assert(scan2.length === 1, `삭제 후: 1개 (got ${scan2.length})`);
|
|
180
|
+
assert(scan2[0].name === "keep", "남은 스킬: keep");
|
|
181
|
+
}
|
|
182
|
+
cleanup(tmpDir);
|
|
183
|
+
// ─── 결과 ──────────────────────────────────────────────────
|
|
184
|
+
console.log(`\n${"─".repeat(40)}`);
|
|
185
|
+
console.log(`총 ${passed + failed}개 테스트: ✅ ${passed} passed, ❌ ${failed} failed\n`);
|
|
186
|
+
process.exit(failed > 0 ? 1 : 0);
|
package/dist/database.d.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SEMO CLI - PostgreSQL 데이터베이스 클라이언트
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 스킬, 커맨드, 에이전트 정보를 조회합니다.
|
|
5
5
|
* DB 연결 실패 시 하드코딩된 폴백 데이터를 사용합니다.
|
|
6
6
|
*
|
|
7
7
|
* v3.14.0: Supabase → PostgreSQL 전환
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* v4.0.0: 스키마 통합 — SoT를 *_definitions 테이블로 변경
|
|
9
|
+
* - skill_definitions (prompt as content)
|
|
10
|
+
* - agent_definitions (persona_prompt as content)
|
|
11
|
+
* - command_definitions (prompt as content)
|
|
12
|
+
* - semo.skills / semo.agents / semo.commands 는 하위 호환 뷰
|
|
10
13
|
*/
|
|
11
14
|
import { Pool } from "pg";
|
|
12
15
|
export declare function getPool(): Pool;
|
|
@@ -16,6 +19,7 @@ export interface Skill {
|
|
|
16
19
|
display_name: string;
|
|
17
20
|
description: string | null;
|
|
18
21
|
content: string;
|
|
22
|
+
bot_ids: string[];
|
|
19
23
|
category: string;
|
|
20
24
|
package: string;
|
|
21
25
|
is_active: boolean;
|
|
@@ -52,20 +56,46 @@ export interface Package {
|
|
|
52
56
|
is_required: boolean;
|
|
53
57
|
install_order: number;
|
|
54
58
|
}
|
|
59
|
+
export interface BotDelegation {
|
|
60
|
+
id: number;
|
|
61
|
+
from_bot_id: string;
|
|
62
|
+
to_bot_id: string;
|
|
63
|
+
delegation_type: string;
|
|
64
|
+
domains: string[];
|
|
65
|
+
method: string;
|
|
66
|
+
channel: string | null;
|
|
67
|
+
max_roundtrips: number;
|
|
68
|
+
priority: string;
|
|
69
|
+
is_active: boolean;
|
|
70
|
+
}
|
|
71
|
+
export interface BotProtocol {
|
|
72
|
+
id: number;
|
|
73
|
+
key: string;
|
|
74
|
+
value: Record<string, unknown>;
|
|
75
|
+
description: string | null;
|
|
76
|
+
}
|
|
55
77
|
/**
|
|
56
78
|
* 활성 스킬 목록 조회
|
|
79
|
+
* SoT: skill_definitions (prompt as content — CLI 인터페이스 유지)
|
|
57
80
|
*/
|
|
58
81
|
export declare function getActiveSkills(): Promise<Skill[]>;
|
|
59
82
|
/**
|
|
60
83
|
* 스킬 이름 목록만 조회
|
|
61
84
|
*/
|
|
62
85
|
export declare function getActiveSkillNames(): Promise<string[]>;
|
|
86
|
+
/**
|
|
87
|
+
* 특정 봇의 스킬 목록 조회 (공유 + 봇 전용)
|
|
88
|
+
* skill_definitions.target_agents 배열로 매핑 — 전용 스킬 먼저, 공유 스킬 뒤
|
|
89
|
+
*/
|
|
90
|
+
export declare function getActiveSkillsForBot(botId: string): Promise<Skill[]>;
|
|
63
91
|
/**
|
|
64
92
|
* 커맨드 목록 조회
|
|
93
|
+
* SoT: command_definitions (prompt as content — CLI 인터페이스 유지)
|
|
65
94
|
*/
|
|
66
95
|
export declare function getCommands(): Promise<SemoCommand[]>;
|
|
67
96
|
/**
|
|
68
97
|
* 에이전트 목록 조회
|
|
98
|
+
* SoT: agent_definitions (persona_prompt as content — CLI 인터페이스 유지)
|
|
69
99
|
*/
|
|
70
100
|
export declare function getAgents(): Promise<Agent[]>;
|
|
71
101
|
/**
|
|
@@ -76,6 +106,14 @@ export declare function getPackages(layer?: string): Promise<Package[]>;
|
|
|
76
106
|
* 카테고리별 스킬 개수 조회
|
|
77
107
|
*/
|
|
78
108
|
export declare function getSkillCountByCategory(): Promise<Record<string, number>>;
|
|
109
|
+
/**
|
|
110
|
+
* 위임 매트릭스 조회
|
|
111
|
+
*/
|
|
112
|
+
export declare function getDelegations(botId?: string): Promise<BotDelegation[]>;
|
|
113
|
+
/**
|
|
114
|
+
* 프로토콜 메타데이터 조회
|
|
115
|
+
*/
|
|
116
|
+
export declare function getProtocol(): Promise<BotProtocol[]>;
|
|
79
117
|
/**
|
|
80
118
|
* DB 연결 종료
|
|
81
119
|
*/
|