@team-semicolon/semo-cli 3.14.1 → 4.0.1

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,433 @@
1
+ "use strict";
2
+ /**
3
+ * semo get <resource> — 세션 중 실시간 DB 쿼리
4
+ *
5
+ * semo get projects [--active]
6
+ * semo get bots [--status online|offline]
7
+ * semo get kb [--domain <d>] [--key <k>] [--search <text>]
8
+ * semo get ontology [--domain <d>]
9
+ * semo get tasks [--project <p>] [--status <s>]
10
+ * semo get sessions [--bot <n>]
11
+ */
12
+ var __importDefault = (this && this.__importDefault) || function (mod) {
13
+ return (mod && mod.__esModule) ? mod : { "default": mod };
14
+ };
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.registerGetCommands = registerGetCommands;
17
+ const chalk_1 = __importDefault(require("chalk"));
18
+ const ora_1 = __importDefault(require("ora"));
19
+ const database_1 = require("../database");
20
+ const kb_1 = require("../kb");
21
+ // ============================================================
22
+ // Formatters
23
+ // ============================================================
24
+ function printTable(headers, rows, title) {
25
+ if (title)
26
+ console.log(chalk_1.default.cyan.bold(`\n${title}\n`));
27
+ if (rows.length === 0) {
28
+ console.log(chalk_1.default.yellow(" 결과 없음\n"));
29
+ return;
30
+ }
31
+ // Column widths
32
+ const widths = headers.map((h, i) => Math.max(h.length, ...rows.map(r => String(r[i] || "").length)));
33
+ const divider = " " + widths.map(w => "─".repeat(w + 2)).join("┬");
34
+ const header = " " + headers.map((h, i) => ` ${h.padEnd(widths[i])} `).join("│");
35
+ console.log(chalk_1.default.gray(divider));
36
+ console.log(chalk_1.default.gray(header));
37
+ console.log(chalk_1.default.gray(divider));
38
+ for (const row of rows) {
39
+ const line = " " + row.map((cell, i) => ` ${String(cell || "").padEnd(widths[i])} `).join("│");
40
+ console.log(line);
41
+ }
42
+ console.log(chalk_1.default.gray(divider));
43
+ console.log(chalk_1.default.gray(` ${rows.length}행\n`));
44
+ }
45
+ // ============================================================
46
+ // Command registration
47
+ // ============================================================
48
+ function registerGetCommands(program) {
49
+ const getCmd = program
50
+ .command("get")
51
+ .description("Core DB에서 리소스 실시간 조회");
52
+ // ── semo get projects ───────────────────────────────────────
53
+ getCmd
54
+ .command("projects")
55
+ .description("프로젝트 목록 조회")
56
+ .option("--active", "활성 프로젝트만")
57
+ .option("--format <type>", "출력 형식 (table|json|md)", "table")
58
+ .action(async (options) => {
59
+ const spinner = (0, ora_1.default)("프로젝트 조회 중...").start();
60
+ const connected = await (0, database_1.isDbConnected)();
61
+ if (!connected) {
62
+ // Fallback: show KB entries with domain=project
63
+ try {
64
+ const pool = (0, database_1.getPool)();
65
+ const { shared } = await (0, kb_1.kbList)(pool, { domain: "project", limit: 100 });
66
+ spinner.stop();
67
+ if (options.format === "json") {
68
+ console.log(JSON.stringify(shared, null, 2));
69
+ }
70
+ else {
71
+ printTable(["key", "content"], shared.map(e => [e.key, e.content.substring(0, 80)]), "📁 프로젝트 (KB 기반)");
72
+ }
73
+ }
74
+ catch {
75
+ spinner.fail("DB 연결 실패");
76
+ }
77
+ await (0, database_1.closeConnection)();
78
+ return;
79
+ }
80
+ try {
81
+ const pool = (0, database_1.getPool)();
82
+ const client = await pool.connect();
83
+ let query = `
84
+ SELECT id, name, display_name, status, description, updated_at::text
85
+ FROM semo.projects
86
+ `;
87
+ const params = [];
88
+ if (options.active) {
89
+ query += " WHERE status = 'active'";
90
+ }
91
+ query += " ORDER BY updated_at DESC";
92
+ let rows = [];
93
+ try {
94
+ const result = await client.query(query, params);
95
+ rows = result.rows;
96
+ }
97
+ catch {
98
+ // semo.projects table may not exist — fallback to KB
99
+ client.release();
100
+ const { shared } = await (0, kb_1.kbList)(pool, { domain: "project", limit: 100 });
101
+ spinner.stop();
102
+ if (options.format === "json") {
103
+ console.log(JSON.stringify(shared, null, 2));
104
+ }
105
+ else {
106
+ printTable(["key", "content"], shared.map(e => [e.key, e.content.substring(0, 80)]), "📁 프로젝트 (KB 기반)");
107
+ }
108
+ await (0, database_1.closeConnection)();
109
+ return;
110
+ }
111
+ client.release();
112
+ spinner.stop();
113
+ if (options.format === "json") {
114
+ console.log(JSON.stringify(rows, null, 2));
115
+ }
116
+ else {
117
+ printTable(["ID", "이름", "상태", "설명"], rows.map(r => [r.id, r.display_name || r.name, r.status || "-", (r.description || "").substring(0, 60)]), "📁 프로젝트");
118
+ }
119
+ }
120
+ catch (err) {
121
+ spinner.fail(`조회 실패: ${err}`);
122
+ process.exit(1);
123
+ }
124
+ finally {
125
+ await (0, database_1.closeConnection)();
126
+ }
127
+ });
128
+ // ── semo get bots ───────────────────────────────────────────
129
+ getCmd
130
+ .command("bots")
131
+ .description("봇 상태 조회")
132
+ .option("--status <filter>", "상태 필터 (online|offline)")
133
+ .option("--format <type>", "출력 형식 (table|json)", "table")
134
+ .action(async (options) => {
135
+ const spinner = (0, ora_1.default)("봇 상태 조회 중...").start();
136
+ const connected = await (0, database_1.isDbConnected)();
137
+ if (!connected) {
138
+ spinner.fail("DB 연결 실패");
139
+ await (0, database_1.closeConnection)();
140
+ process.exit(1);
141
+ }
142
+ try {
143
+ const pool = (0, database_1.getPool)();
144
+ const client = await pool.connect();
145
+ let query = `
146
+ SELECT bot_id, name, emoji, role, status,
147
+ last_active::text, session_count, synced_at::text
148
+ FROM semo.bot_status
149
+ `;
150
+ const params = [];
151
+ if (options.status) {
152
+ query += " WHERE status = $1";
153
+ params.push(options.status);
154
+ }
155
+ query += " ORDER BY bot_id";
156
+ const result = await client.query(query, params);
157
+ client.release();
158
+ spinner.stop();
159
+ if (options.format === "json") {
160
+ console.log(JSON.stringify(result.rows, null, 2));
161
+ }
162
+ else {
163
+ printTable(["bot_id", "이름", "status", "last_active", "sessions"], result.rows.map((r) => [
164
+ r.bot_id,
165
+ [r.emoji, r.name].filter(Boolean).join(" ") || r.bot_id,
166
+ r.status || "-",
167
+ r.last_active ? new Date(r.last_active).toLocaleString("ko-KR") : "-",
168
+ String(r.session_count || 0),
169
+ ]), "🤖 봇 상태");
170
+ }
171
+ }
172
+ catch (err) {
173
+ spinner.fail(`조회 실패: ${err}`);
174
+ process.exit(1);
175
+ }
176
+ finally {
177
+ await (0, database_1.closeConnection)();
178
+ }
179
+ });
180
+ // ── semo get kb ─────────────────────────────────────────────
181
+ getCmd
182
+ .command("kb")
183
+ .description("Knowledge Base 조회")
184
+ .option("--domain <name>", "도메인 필터")
185
+ .option("--key <name>", "키 검색")
186
+ .option("--search <text>", "하이브리드 검색")
187
+ .option("--limit <n>", "최대 결과 수", "20")
188
+ .option("--format <type>", "출력 형식 (table|json|md)", "table")
189
+ .action(async (options) => {
190
+ const spinner = (0, ora_1.default)("KB 조회 중...").start();
191
+ const connected = await (0, database_1.isDbConnected)();
192
+ if (!connected) {
193
+ spinner.fail("DB 연결 실패");
194
+ await (0, database_1.closeConnection)();
195
+ process.exit(1);
196
+ }
197
+ const pool = (0, database_1.getPool)();
198
+ try {
199
+ let entries = [];
200
+ const limit = parseInt(options.limit);
201
+ if (options.search) {
202
+ const results = await (0, kb_1.kbSearch)(pool, options.search, {
203
+ domain: options.domain,
204
+ limit,
205
+ });
206
+ entries = results;
207
+ }
208
+ else if (options.key) {
209
+ const client = await pool.connect();
210
+ const result = await client.query(`SELECT domain, key, content, metadata, updated_at::text
211
+ FROM semo.knowledge_base
212
+ WHERE key ILIKE $1${options.domain ? " AND domain = $2" : ""}
213
+ ORDER BY domain, key LIMIT $${options.domain ? 3 : 2}`, options.domain
214
+ ? [`%${options.key}%`, options.domain, limit]
215
+ : [`%${options.key}%`, limit]);
216
+ client.release();
217
+ entries = result.rows;
218
+ }
219
+ else {
220
+ const { shared } = await (0, kb_1.kbList)(pool, {
221
+ domain: options.domain,
222
+ limit,
223
+ });
224
+ entries = shared;
225
+ }
226
+ spinner.stop();
227
+ if (options.format === "json") {
228
+ console.log(JSON.stringify(entries, null, 2));
229
+ }
230
+ else if (options.format === "md") {
231
+ for (const e of entries) {
232
+ console.log(`\n## ${e["domain"]}/${e["key"]}\n`);
233
+ console.log(e["content"]);
234
+ }
235
+ }
236
+ else {
237
+ printTable(["domain", "key", "content"], entries.map(e => [
238
+ e["domain"] || "",
239
+ e["key"] || "",
240
+ (e["content"] || "").substring(0, 60),
241
+ ]), "📚 Knowledge Base");
242
+ }
243
+ }
244
+ catch (err) {
245
+ spinner.fail(`조회 실패: ${err}`);
246
+ process.exit(1);
247
+ }
248
+ finally {
249
+ await (0, database_1.closeConnection)();
250
+ }
251
+ });
252
+ // ── semo get ontology ───────────────────────────────────────
253
+ getCmd
254
+ .command("ontology")
255
+ .description("온톨로지 도메인 조회")
256
+ .option("--domain <name>", "특정 도메인 상세")
257
+ .option("--format <type>", "출력 형식 (table|json)", "table")
258
+ .action(async (options) => {
259
+ const spinner = (0, ora_1.default)("온톨로지 조회 중...").start();
260
+ const connected = await (0, database_1.isDbConnected)();
261
+ if (!connected) {
262
+ spinner.fail("DB 연결 실패");
263
+ await (0, database_1.closeConnection)();
264
+ process.exit(1);
265
+ }
266
+ const pool = (0, database_1.getPool)();
267
+ try {
268
+ if (options.domain) {
269
+ const onto = await (0, kb_1.ontoShow)(pool, options.domain);
270
+ spinner.stop();
271
+ if (!onto) {
272
+ console.log(chalk_1.default.red(`\n 온톨로지 '${options.domain}'을 찾을 수 없습니다.\n`));
273
+ }
274
+ else if (options.format === "json") {
275
+ console.log(JSON.stringify(onto, null, 2));
276
+ }
277
+ else {
278
+ console.log(chalk_1.default.cyan.bold(`\n📐 ${onto.domain} (v${onto.version})\n`));
279
+ if (onto.description)
280
+ console.log(chalk_1.default.gray(` ${onto.description}\n`));
281
+ console.log(JSON.stringify(onto.schema, null, 2).split("\n").map(l => " " + l).join("\n"));
282
+ console.log();
283
+ }
284
+ }
285
+ else {
286
+ const domains = await (0, kb_1.ontoList)(pool);
287
+ spinner.stop();
288
+ if (options.format === "json") {
289
+ console.log(JSON.stringify(domains, null, 2));
290
+ }
291
+ else {
292
+ printTable(["domain", "version", "description"], domains.map(d => [d.domain, String(d.version), d.description || "-"]), "📐 온톨로지 도메인");
293
+ }
294
+ }
295
+ }
296
+ catch (err) {
297
+ spinner.fail(`조회 실패: ${err}`);
298
+ process.exit(1);
299
+ }
300
+ finally {
301
+ await (0, database_1.closeConnection)();
302
+ }
303
+ });
304
+ // ── semo get tasks ──────────────────────────────────────────
305
+ getCmd
306
+ .command("tasks")
307
+ .description("태스크 조회 (semo.tasks)")
308
+ .option("--project <name>", "프로젝트 필터")
309
+ .option("--status <s>", "상태 필터 (open|in_progress|done)")
310
+ .option("--limit <n>", "최대 결과 수", "20")
311
+ .option("--format <type>", "출력 형식 (table|json)", "table")
312
+ .action(async (options) => {
313
+ const spinner = (0, ora_1.default)("태스크 조회 중...").start();
314
+ const connected = await (0, database_1.isDbConnected)();
315
+ if (!connected) {
316
+ spinner.fail("DB 연결 실패");
317
+ await (0, database_1.closeConnection)();
318
+ process.exit(1);
319
+ }
320
+ try {
321
+ const pool = (0, database_1.getPool)();
322
+ const client = await pool.connect();
323
+ let query = "SELECT id, title, status, project_id, assignee_name, updated_at::text FROM semo.tasks";
324
+ const params = [];
325
+ const conditions = [];
326
+ let idx = 1;
327
+ if (options.project) {
328
+ conditions.push(`project_id = $${idx++}`);
329
+ params.push(options.project);
330
+ }
331
+ if (options.status) {
332
+ conditions.push(`status = $${idx++}`);
333
+ params.push(options.status);
334
+ }
335
+ if (conditions.length > 0) {
336
+ query += " WHERE " + conditions.join(" AND ");
337
+ }
338
+ query += ` ORDER BY updated_at DESC LIMIT $${idx++}`;
339
+ params.push(parseInt(options.limit));
340
+ let rows = [];
341
+ try {
342
+ const result = await client.query(query, params);
343
+ rows = result.rows;
344
+ }
345
+ catch {
346
+ client.release();
347
+ spinner.warn("semo.tasks 테이블이 없거나 접근 불가");
348
+ await (0, database_1.closeConnection)();
349
+ return;
350
+ }
351
+ client.release();
352
+ spinner.stop();
353
+ if (options.format === "json") {
354
+ console.log(JSON.stringify(rows, null, 2));
355
+ }
356
+ else {
357
+ printTable(["id", "title", "status", "assignee"], rows.map(r => [r.id, (r.title || "").substring(0, 50), r.status, r.assignee_name || "-"]), "📋 태스크");
358
+ }
359
+ }
360
+ catch (err) {
361
+ spinner.fail(`조회 실패: ${err}`);
362
+ process.exit(1);
363
+ }
364
+ finally {
365
+ await (0, database_1.closeConnection)();
366
+ }
367
+ });
368
+ // ── semo get sessions ───────────────────────────────────────
369
+ getCmd
370
+ .command("sessions")
371
+ .description("봇 세션 조회 (semo.bot_sessions)")
372
+ .option("--bot <name>", "봇 ID 필터")
373
+ .option("--limit <n>", "최대 결과 수", "10")
374
+ .option("--format <type>", "출력 형식 (table|json)", "table")
375
+ .action(async (options) => {
376
+ const spinner = (0, ora_1.default)("세션 조회 중...").start();
377
+ const connected = await (0, database_1.isDbConnected)();
378
+ if (!connected) {
379
+ spinner.fail("DB 연결 실패");
380
+ await (0, database_1.closeConnection)();
381
+ process.exit(1);
382
+ }
383
+ try {
384
+ const pool = (0, database_1.getPool)();
385
+ const client = await pool.connect();
386
+ let query = `
387
+ SELECT bot_id, session_key, label, kind, chat_type,
388
+ last_activity::text, message_count
389
+ FROM semo.bot_sessions
390
+ `;
391
+ const params = [];
392
+ let idx = 1;
393
+ if (options.bot) {
394
+ query += ` WHERE bot_id = $${idx++}`;
395
+ params.push(options.bot);
396
+ }
397
+ query += ` ORDER BY last_activity DESC NULLS LAST LIMIT $${idx++}`;
398
+ params.push(parseInt(options.limit));
399
+ let rows = [];
400
+ try {
401
+ const result = await client.query(query, params);
402
+ rows = result.rows;
403
+ }
404
+ catch {
405
+ client.release();
406
+ spinner.warn("semo.bot_sessions 테이블이 없거나 접근 불가");
407
+ await (0, database_1.closeConnection)();
408
+ return;
409
+ }
410
+ client.release();
411
+ spinner.stop();
412
+ if (options.format === "json") {
413
+ console.log(JSON.stringify(rows, null, 2));
414
+ }
415
+ else {
416
+ printTable(["bot_id", "session_key", "label", "last_activity", "msgs"], rows.map(r => [
417
+ r.bot_id,
418
+ r.session_key || "-",
419
+ r.label || "-",
420
+ r.last_activity ? new Date(r.last_activity).toLocaleString("ko-KR") : "-",
421
+ String(r.message_count || 0),
422
+ ]), "📋 봇 세션");
423
+ }
424
+ }
425
+ catch (err) {
426
+ spinner.fail(`조회 실패: ${err}`);
427
+ process.exit(1);
428
+ }
429
+ finally {
430
+ await (0, database_1.closeConnection)();
431
+ }
432
+ });
433
+ }
@@ -8,6 +8,8 @@
8
8
  * - semo-system 의존성 제거
9
9
  * - 문서 내용을 DB에서 직접 조회
10
10
  */
11
+ import { Pool } from "pg";
12
+ export declare function getPool(): Pool;
11
13
  export interface Skill {
12
14
  id: string;
13
15
  name: string;
package/dist/database.js CHANGED
@@ -10,6 +10,7 @@
10
10
  * - 문서 내용을 DB에서 직접 조회
11
11
  */
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.getPool = getPool;
13
14
  exports.getActiveSkills = getActiveSkills;
14
15
  exports.getActiveSkillNames = getActiveSkillNames;
15
16
  exports.getCommands = getCommands;
@@ -20,16 +21,31 @@ exports.closeConnection = closeConnection;
20
21
  exports.isDbConnected = isDbConnected;
21
22
  const pg_1 = require("pg");
22
23
  // PostgreSQL 연결 정보 (팀 코어)
23
- const DB_CONFIG = {
24
- host: process.env.SEMO_DB_HOST || "3.38.162.21",
25
- port: parseInt(process.env.SEMO_DB_PORT || "5432"),
26
- user: process.env.SEMO_DB_USER || "app",
27
- password: process.env.SEMO_DB_PASSWORD || "ProductionPassword2024!@#",
28
- database: process.env.SEMO_DB_NAME || "appdb",
29
- ssl: false,
30
- connectionTimeoutMillis: 5000,
31
- idleTimeoutMillis: 30000,
32
- };
24
+ // DATABASE_URL 우선, 없으면 개별 SEMO_DB_* 변수 사용
25
+ function buildDbConfig() {
26
+ if (process.env.DATABASE_URL) {
27
+ return {
28
+ connectionString: process.env.DATABASE_URL,
29
+ ssl: process.env.DATABASE_URL.includes("sslmode=require") ? { rejectUnauthorized: false } : false,
30
+ connectionTimeoutMillis: 5000,
31
+ idleTimeoutMillis: 30000,
32
+ };
33
+ }
34
+ if (!process.env.SEMO_DB_HOST && !process.env.DATABASE_URL) {
35
+ throw new Error("DB 연결 정보가 없습니다. DATABASE_URL 또는 SEMO_DB_HOST 환경변수를 설정하세요.");
36
+ }
37
+ return {
38
+ host: process.env.SEMO_DB_HOST,
39
+ port: parseInt(process.env.SEMO_DB_PORT || "5432"),
40
+ user: process.env.SEMO_DB_USER || "app",
41
+ password: process.env.SEMO_DB_PASSWORD,
42
+ database: process.env.SEMO_DB_NAME || "appdb",
43
+ ssl: false,
44
+ connectionTimeoutMillis: 5000,
45
+ idleTimeoutMillis: 30000,
46
+ };
47
+ }
48
+ const DB_CONFIG = buildDbConfig();
33
49
  // PostgreSQL Pool (싱글톤)
34
50
  let pool = null;
35
51
  function getPool() {
package/dist/index.d.ts CHANGED
@@ -1,16 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * SEMO CLI v2.0
3
+ * SEMO CLI v4.0
4
4
  *
5
- * Gemini 하이브리드 전략 기반 AI Agent Orchestration Framework
5
+ * Core DB 기반 컨텍스트 동기화 시스템
6
6
  *
7
7
  * 사용법:
8
- * npx @team-semicolon/semo-cli init # 기본 설치
9
- * npx @team-semicolon/semo-cli add next # 패키지 추가
10
- * npx @team-semicolon/semo-cli list # 패키지 목록
11
- *
12
- * 구조:
13
- * - Standard: semo-core + semo-skills (필수)
14
- * - Extensions: packages/next, packages/backend 등 (선택)
8
+ * npx @team-semicolon/semo-cli init # 기본 설치 (훅 등록 포함)
9
+ * npx @team-semicolon/semo-cli context sync # DB → .claude/memory/
10
+ * npx @team-semicolon/semo-cli bots status # 상태 조회
11
+ * npx @team-semicolon/semo-cli get kb # KB 실시간 쿼리
15
12
  */
16
13
  export {};