@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.
@@ -0,0 +1,5 @@
1
+ /**
2
+ * 공용 KEY=VALUE 파서
3
+ * ~/.semo.env 파일과 GitHub Gist 콘텐츠 모두 이 함수로 파싱한다.
4
+ */
5
+ export declare function parseEnvContent(content: string): Record<string, string>;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseEnvContent = parseEnvContent;
4
+ /**
5
+ * 공용 KEY=VALUE 파서
6
+ * ~/.semo.env 파일과 GitHub Gist 콘텐츠 모두 이 함수로 파싱한다.
7
+ */
8
+ function parseEnvContent(content) {
9
+ const result = {};
10
+ for (const raw of content.split("\n")) {
11
+ const line = raw.trim().replace(/^export\s+/, "");
12
+ if (!line || line.startsWith("#"))
13
+ continue;
14
+ const eqIdx = line.indexOf("=");
15
+ if (eqIdx === -1)
16
+ continue;
17
+ const key = line.slice(0, eqIdx).trim();
18
+ let val = line.slice(eqIdx + 1).trim();
19
+ if ((val.startsWith("'") && val.endsWith("'")) ||
20
+ (val.startsWith('"') && val.endsWith('"'))) {
21
+ val = val.slice(1, -1);
22
+ }
23
+ if (key)
24
+ result[key] = val;
25
+ }
26
+ return result;
27
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * global-cache — DB → ~/.claude/{skills,commands,agents} 동기화
3
+ *
4
+ * SessionStart 훅과 `semo context sync`에서 호출.
5
+ * 기존 디렉토리를 전체 교체(full replace)하며, DB 실패 시 기존 파일 유지.
6
+ */
7
+ export interface GlobalCacheSyncResult {
8
+ skills: number;
9
+ commands: number;
10
+ agents: number;
11
+ }
12
+ export declare function syncGlobalCache(claudeDir?: string): Promise<GlobalCacheSyncResult>;
@@ -0,0 +1,184 @@
1
+ "use strict";
2
+ /**
3
+ * global-cache — DB → ~/.claude/{skills,commands,agents} 동기화
4
+ *
5
+ * SessionStart 훅과 `semo context sync`에서 호출.
6
+ * 기존 디렉토리를 전체 교체(full replace)하며, DB 실패 시 기존 파일 유지.
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.syncGlobalCache = syncGlobalCache;
43
+ const fs = __importStar(require("fs"));
44
+ const path = __importStar(require("path"));
45
+ const os = __importStar(require("os"));
46
+ const child_process_1 = require("child_process");
47
+ const database_1 = require("./database");
48
+ const isWindows = process.platform === "win32";
49
+ function removeRecursive(targetPath) {
50
+ if (!fs.existsSync(targetPath))
51
+ return;
52
+ if (isWindows) {
53
+ try {
54
+ const stats = fs.lstatSync(targetPath);
55
+ if (stats.isSymbolicLink()) {
56
+ (0, child_process_1.execSync)(`cmd /c "rmdir "${targetPath}""`, { stdio: "pipe" });
57
+ }
58
+ else {
59
+ (0, child_process_1.execSync)(`cmd /c "rd /s /q "${targetPath}""`, { stdio: "pipe" });
60
+ }
61
+ }
62
+ catch {
63
+ fs.rmSync(targetPath, { recursive: true, force: true });
64
+ }
65
+ }
66
+ else {
67
+ (0, child_process_1.execSync)(`rm -rf "${targetPath}"`, { stdio: "pipe" });
68
+ }
69
+ }
70
+ /**
71
+ * DB에서 스킬/커맨드/에이전트를 조회하여 ~/.claude/ 하위에 파일로 캐싱.
72
+ *
73
+ * - 병렬 조회 후 기존 디렉토리 삭제 → 재생성 → 파일 쓰기 (full replace)
74
+ * - DB 실패 시 기존 파일 유지 (비치명적 에러)
75
+ * - 콘솔 출력 없음 (caller가 처리)
76
+ */
77
+ /**
78
+ * SKILL.md content에 대상 에이전트(봇) 정보를 주입
79
+ */
80
+ function injectAgentInfo(content, botIds) {
81
+ if (!botIds || botIds.length === 0)
82
+ return content;
83
+ const agentLine = `\n> **Agents:** ${botIds.join(', ')}\n`;
84
+ // frontmatter에 description이 있으면 그 줄 바로 뒤에 Agents 삽입
85
+ const fmEnd = content.indexOf('\n---', 4); // 첫 번째 --- 이후의 ---
86
+ if (content.startsWith('---\n') && fmEnd !== -1) {
87
+ const fm = content.slice(4, fmEnd);
88
+ const descIdx = fm.indexOf('description:');
89
+ if (descIdx !== -1) {
90
+ const absDescStart = 4 + descIdx;
91
+ const lineEnd = content.indexOf('\n', absDescStart);
92
+ if (lineEnd !== -1) {
93
+ return (content.slice(0, lineEnd) +
94
+ `\n Agents: ${botIds.join(', ')}` +
95
+ content.slice(lineEnd));
96
+ }
97
+ }
98
+ }
99
+ // fallback: 첫 번째 빈 줄 뒤에 blockquote로 삽입
100
+ const firstBlank = content.indexOf('\n\n');
101
+ if (firstBlank !== -1) {
102
+ return content.slice(0, firstBlank) + agentLine + content.slice(firstBlank);
103
+ }
104
+ return content + agentLine;
105
+ }
106
+ async function syncGlobalCache(claudeDir) {
107
+ const dir = claudeDir || path.join(os.homedir(), ".claude");
108
+ fs.mkdirSync(dir, { recursive: true });
109
+ // 병렬 조회
110
+ const [skills, commands, agents, delegations] = await Promise.all([
111
+ (0, database_1.getActiveSkills)(),
112
+ (0, database_1.getCommands)(),
113
+ (0, database_1.getAgents)(),
114
+ (0, database_1.getDelegations)(),
115
+ ]);
116
+ // 1. 스킬 설치 (전체 교체)
117
+ const skillsDir = path.join(dir, "skills");
118
+ removeRecursive(skillsDir);
119
+ fs.mkdirSync(skillsDir, { recursive: true });
120
+ let skippedSkills = 0;
121
+ for (const skill of skills) {
122
+ if (skill.name.includes('/')) {
123
+ console.warn(`⚠️ 스킬 이름에 슬래시 포함 — 스킵: ${skill.name}`);
124
+ skippedSkills++;
125
+ continue;
126
+ }
127
+ const skillFolder = path.join(skillsDir, skill.name);
128
+ fs.mkdirSync(skillFolder, { recursive: true });
129
+ const finalContent = injectAgentInfo(skill.content, skill.bot_ids);
130
+ fs.writeFileSync(path.join(skillFolder, "SKILL.md"), finalContent);
131
+ }
132
+ // 2. 커맨드 설치 (전체 교체)
133
+ const commandsDir = path.join(dir, "commands");
134
+ removeRecursive(commandsDir);
135
+ fs.mkdirSync(commandsDir, { recursive: true });
136
+ const commandsByFolder = {};
137
+ for (const cmd of commands) {
138
+ if (!commandsByFolder[cmd.folder]) {
139
+ commandsByFolder[cmd.folder] = [];
140
+ }
141
+ commandsByFolder[cmd.folder].push(cmd);
142
+ }
143
+ let cmdCount = 0;
144
+ for (const [folder, cmds] of Object.entries(commandsByFolder)) {
145
+ const folderPath = path.join(commandsDir, folder);
146
+ fs.mkdirSync(folderPath, { recursive: true });
147
+ for (const cmd of cmds) {
148
+ fs.writeFileSync(path.join(folderPath, `${cmd.name}.md`), cmd.content);
149
+ cmdCount++;
150
+ }
151
+ }
152
+ // 3. 에이전트 설치 (전체 교체, 대소문자 중복 제거)
153
+ const agentsDir = path.join(dir, "agents");
154
+ removeRecursive(agentsDir);
155
+ fs.mkdirSync(agentsDir, { recursive: true });
156
+ const seenAgentNames = new Set();
157
+ const dedupedAgents = [];
158
+ for (const agent of agents) {
159
+ const lowerName = agent.name.toLowerCase();
160
+ if (!seenAgentNames.has(lowerName)) {
161
+ seenAgentNames.add(lowerName);
162
+ dedupedAgents.push(agent);
163
+ }
164
+ }
165
+ for (const agent of dedupedAgents) {
166
+ const agentFolder = path.join(agentsDir, agent.name);
167
+ fs.mkdirSync(agentFolder, { recursive: true });
168
+ let content = agent.content;
169
+ // 위임 매트릭스 주입
170
+ const agentDelegations = delegations.filter((d) => d.from_bot_id === agent.name);
171
+ if (agentDelegations.length > 0) {
172
+ const delegationLines = agentDelegations
173
+ .map((d) => `- → ${d.to_bot_id}: ${d.domains.join(", ")} (via ${d.method})`)
174
+ .join("\n");
175
+ content += `\n\n## 위임 매트릭스\n${delegationLines}\n`;
176
+ }
177
+ fs.writeFileSync(path.join(agentFolder, `${agent.name}.md`), content);
178
+ }
179
+ return {
180
+ skills: skills.length - skippedSkills,
181
+ commands: cmdCount,
182
+ agents: dedupedAgents.length,
183
+ };
184
+ }