@team-semicolon/semo-cli 4.10.0 → 4.11.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/dist/index.js CHANGED
@@ -1733,7 +1733,7 @@ kbCmd
1733
1733
  kbCmd
1734
1734
  .command("ontology")
1735
1735
  .description("온톨로지 조회 — 도메인/타입/스키마/라우팅 테이블")
1736
- .option("--action <type>", "동작 (list|show|services|types|instances|schema|routing-table|register|create-type|add-key|remove-key)", "list")
1736
+ .option("--action <type>", "동작 (list|show|services|types|instances|schema|routing-table|register|unregister|create-type|add-key|remove-key)", "list")
1737
1737
  .option("--domain <name>", "action=show|register 시 도메인")
1738
1738
  .option("--type <name>", "action=schema|register|add-key|remove-key 시 타입 키")
1739
1739
  .option("--key <name>", "action=add-key|remove-key 시 스키마 키")
@@ -1744,6 +1744,8 @@ kbCmd
1744
1744
  .option("--service <name>", "action=register 시 서비스 그룹")
1745
1745
  .option("--tags <tags>", "action=register 시 태그 (쉼표 구분)")
1746
1746
  .option("--no-init", "action=register 시 필수 KB entry 자동 생성 건너뛰기")
1747
+ .option("--force", "action=unregister 시 잔존 KB 항목도 모두 삭제")
1748
+ .option("--yes", "action=unregister 시 확인 프롬프트 건너뛰기")
1747
1749
  .option("--format <type>", "출력 형식 (json|table)", "table")
1748
1750
  .action(async (options) => {
1749
1751
  try {
@@ -1989,8 +1991,64 @@ kbCmd
1989
1991
  process.exit(1);
1990
1992
  }
1991
1993
  }
1994
+ else if (action === "unregister") {
1995
+ if (!options.domain) {
1996
+ console.log(chalk_1.default.red("--domain 옵션이 필요합니다."));
1997
+ process.exit(1);
1998
+ }
1999
+ // 도메인 정보 조회
2000
+ const domainInfo = await (0, kb_1.ontoShow)(pool, options.domain);
2001
+ if (!domainInfo) {
2002
+ console.log(chalk_1.default.red(`\n❌ 도메인 '${options.domain}'은(는) 존재하지 않습니다.\n`));
2003
+ process.exit(1);
2004
+ }
2005
+ // KB 엔트리 수 확인
2006
+ const countRes = await pool.query("SELECT COUNT(*)::int AS cnt FROM semo.knowledge_base WHERE domain = $1", [options.domain]);
2007
+ const kbCount = countRes.rows[0].cnt;
2008
+ // 확인 프롬프트
2009
+ if (!options.yes) {
2010
+ const typeStr = domainInfo.entity_type ? ` [${domainInfo.entity_type}]` : "";
2011
+ const svcStr = domainInfo.service && domainInfo.service !== "_global" ? ` (${domainInfo.service})` : "";
2012
+ if (kbCount > 0 && options.force) {
2013
+ console.log(chalk_1.default.yellow(`\n⚠️ 도메인 '${options.domain}'에 KB 항목 ${kbCount}건이 남아있습니다.`));
2014
+ console.log(chalk_1.default.yellow(` --force 옵션으로 모두 삭제됩니다.\n`));
2015
+ }
2016
+ else {
2017
+ console.log(chalk_1.default.cyan(`\n📐 삭제 대상 도메인: ${options.domain}${typeStr}${svcStr}`));
2018
+ console.log(chalk_1.default.gray(` KB 항목: ${kbCount}건\n`));
2019
+ }
2020
+ const { createInterface } = await import("readline");
2021
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
2022
+ const answer = await new Promise((resolve) => {
2023
+ rl.question(chalk_1.default.yellow(" 정말 삭제하시겠습니까? (y/N): "), resolve);
2024
+ });
2025
+ rl.close();
2026
+ if (answer.toLowerCase() !== "y") {
2027
+ console.log(chalk_1.default.gray(" 취소됨.\n"));
2028
+ await (0, database_1.closeConnection)();
2029
+ return;
2030
+ }
2031
+ }
2032
+ const result = await (0, kb_1.ontoUnregister)(pool, options.domain, !!options.force);
2033
+ if (options.format === "json") {
2034
+ console.log(JSON.stringify(result, null, 2));
2035
+ }
2036
+ else {
2037
+ if (result.success) {
2038
+ console.log(chalk_1.default.green(`\n✅ 도메인 '${options.domain}' 삭제 완료`));
2039
+ if (result.deleted_entries && result.deleted_entries > 0) {
2040
+ console.log(chalk_1.default.gray(` KB 항목 ${result.deleted_entries}건 함께 삭제됨`));
2041
+ }
2042
+ console.log();
2043
+ }
2044
+ else {
2045
+ console.log(chalk_1.default.red(`\n❌ 삭제 실패: ${result.error}\n`));
2046
+ process.exit(1);
2047
+ }
2048
+ }
2049
+ }
1992
2050
  else {
1993
- console.log(chalk_1.default.red(`알 수 없는 action: '${action}'. 사용 가능: list, show, services, types, instances, schema, routing-table, register, create-type, add-key, remove-key`));
2051
+ console.log(chalk_1.default.red(`알 수 없는 action: '${action}'. 사용 가능: list, show, services, types, instances, schema, routing-table, register, create-type, add-key, remove-key, unregister`));
1994
2052
  process.exit(1);
1995
2053
  }
1996
2054
  await (0, database_1.closeConnection)();
@@ -2218,6 +2276,74 @@ ontoCmd
2218
2276
  process.exit(1);
2219
2277
  }
2220
2278
  });
2279
+ ontoCmd
2280
+ .command("unregister <domain>")
2281
+ .description("온톨로지 도메인 삭제 (KB 데이터 포함)")
2282
+ .option("--force", "잔존 KB 항목이 있어도 모두 삭제 후 도메인 제거")
2283
+ .option("--yes", "확인 프롬프트 건너뛰기")
2284
+ .option("--format <type>", "출력 형식 (json|table)", "table")
2285
+ .action(async (domain, options) => {
2286
+ try {
2287
+ const pool = (0, database_1.getPool)();
2288
+ // 도메인 정보 조회
2289
+ const domainInfo = await (0, kb_1.ontoShow)(pool, domain);
2290
+ if (!domainInfo) {
2291
+ console.log(chalk_1.default.red(`\n❌ 도메인 '${domain}'은(는) 존재하지 않습니다.\n`));
2292
+ await (0, database_1.closeConnection)();
2293
+ process.exit(1);
2294
+ }
2295
+ // KB 엔트리 수 확인
2296
+ const countRes = await pool.query("SELECT COUNT(*)::int AS cnt FROM semo.knowledge_base WHERE domain = $1", [domain]);
2297
+ const kbCount = countRes.rows[0].cnt;
2298
+ // 확인 프롬프트
2299
+ if (!options.yes) {
2300
+ const typeStr = domainInfo.entity_type ? ` [${domainInfo.entity_type}]` : "";
2301
+ const svcStr = domainInfo.service && domainInfo.service !== "_global" ? ` (${domainInfo.service})` : "";
2302
+ if (kbCount > 0 && options.force) {
2303
+ console.log(chalk_1.default.yellow(`\n⚠️ 도메인 '${domain}'에 KB 항목 ${kbCount}건이 남아있습니다.`));
2304
+ console.log(chalk_1.default.yellow(` --force 옵션으로 모두 삭제됩니다.\n`));
2305
+ }
2306
+ else {
2307
+ console.log(chalk_1.default.cyan(`\n📐 삭제 대상 도메인: ${domain}${typeStr}${svcStr}`));
2308
+ console.log(chalk_1.default.gray(` KB 항목: ${kbCount}건\n`));
2309
+ }
2310
+ const { createInterface } = await import("readline");
2311
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
2312
+ const answer = await new Promise((resolve) => {
2313
+ rl.question(chalk_1.default.yellow(" 정말 삭제하시겠습니까? (y/N): "), resolve);
2314
+ });
2315
+ rl.close();
2316
+ if (answer.toLowerCase() !== "y") {
2317
+ console.log(chalk_1.default.gray(" 취소됨.\n"));
2318
+ await (0, database_1.closeConnection)();
2319
+ return;
2320
+ }
2321
+ }
2322
+ const result = await (0, kb_1.ontoUnregister)(pool, domain, !!options.force);
2323
+ if (options.format === "json") {
2324
+ console.log(JSON.stringify(result, null, 2));
2325
+ }
2326
+ else {
2327
+ if (result.success) {
2328
+ console.log(chalk_1.default.green(`\n✅ 도메인 '${domain}' 삭제 완료`));
2329
+ if (result.deleted_entries && result.deleted_entries > 0) {
2330
+ console.log(chalk_1.default.gray(` KB 항목 ${result.deleted_entries}건 함께 삭제됨`));
2331
+ }
2332
+ console.log();
2333
+ }
2334
+ else {
2335
+ console.log(chalk_1.default.red(`\n❌ 삭제 실패: ${result.error}\n`));
2336
+ process.exit(1);
2337
+ }
2338
+ }
2339
+ await (0, database_1.closeConnection)();
2340
+ }
2341
+ catch (err) {
2342
+ console.error(chalk_1.default.red(`삭제 실패: ${err}`));
2343
+ await (0, database_1.closeConnection)();
2344
+ process.exit(1);
2345
+ }
2346
+ });
2221
2347
  // === 신규 v4 커맨드 그룹 등록 ===
2222
2348
  (0, context_1.registerContextCommands)(program);
2223
2349
  (0, bots_1.registerBotsCommands)(program);
package/dist/kb.d.ts CHANGED
@@ -232,6 +232,17 @@ export interface OntoRegisterResult {
232
232
  * 4. If init_required (default true), create KB entries for required keys in kb_type_schema
233
233
  */
234
234
  export declare function ontoRegister(pool: Pool, opts: OntoRegisterOptions): Promise<OntoRegisterResult>;
235
+ export interface OntoUnregisterResult {
236
+ success: boolean;
237
+ deleted_entries?: number;
238
+ error?: string;
239
+ }
240
+ /**
241
+ * Unregister an ontology domain.
242
+ * If force=false and KB entries exist, returns error with count.
243
+ * If force=true, deletes all KB entries in a transaction, then removes the domain.
244
+ */
245
+ export declare function ontoUnregister(pool: Pool, domain: string, force: boolean): Promise<OntoUnregisterResult>;
235
246
  /**
236
247
  * Add a key to a type schema
237
248
  */
package/dist/kb.js CHANGED
@@ -62,6 +62,7 @@ exports.ontoRoutingTable = ontoRoutingTable;
62
62
  exports.ontoListServices = ontoListServices;
63
63
  exports.ontoListInstances = ontoListInstances;
64
64
  exports.ontoRegister = ontoRegister;
65
+ exports.ontoUnregister = ontoUnregister;
65
66
  exports.ontoAddKey = ontoAddKey;
66
67
  exports.ontoCreateType = ontoCreateType;
67
68
  exports.ontoRemoveKey = ontoRemoveKey;
@@ -1011,6 +1012,50 @@ async function ontoRegister(pool, opts) {
1011
1012
  client.release();
1012
1013
  }
1013
1014
  }
1015
+ /**
1016
+ * Unregister an ontology domain.
1017
+ * If force=false and KB entries exist, returns error with count.
1018
+ * If force=true, deletes all KB entries in a transaction, then removes the domain.
1019
+ */
1020
+ async function ontoUnregister(pool, domain, force) {
1021
+ const client = await pool.connect();
1022
+ try {
1023
+ // 1. Check domain exists
1024
+ const domainCheck = await client.query("SELECT domain, entity_type, service FROM semo.ontology WHERE domain = $1", [domain]);
1025
+ if (domainCheck.rows.length === 0) {
1026
+ return { success: false, error: `도메인 '${domain}'은(는) 존재하지 않습니다.` };
1027
+ }
1028
+ // 2. Count KB entries
1029
+ const countResult = await client.query("SELECT COUNT(*)::int AS cnt FROM semo.knowledge_base WHERE domain = $1", [domain]);
1030
+ const entryCount = countResult.rows[0].cnt;
1031
+ // 3. If entries exist and no force → error
1032
+ if (entryCount > 0 && !force) {
1033
+ return {
1034
+ success: false,
1035
+ error: `도메인 '${domain}'에 KB 항목 ${entryCount}건이 남아있습니다. --force 옵션으로 모두 삭제 후 제거할 수 있습니다.`,
1036
+ };
1037
+ }
1038
+ // 4. Transaction: delete KB entries (if any) → delete ontology
1039
+ await client.query("BEGIN");
1040
+ try {
1041
+ let deletedEntries = 0;
1042
+ if (entryCount > 0) {
1043
+ const delResult = await client.query("DELETE FROM semo.knowledge_base WHERE domain = $1", [domain]);
1044
+ deletedEntries = delResult.rowCount ?? 0;
1045
+ }
1046
+ await client.query("DELETE FROM semo.ontology WHERE domain = $1", [domain]);
1047
+ await client.query("COMMIT");
1048
+ return { success: true, deleted_entries: deletedEntries };
1049
+ }
1050
+ catch (err) {
1051
+ await client.query("ROLLBACK");
1052
+ return { success: false, error: String(err) };
1053
+ }
1054
+ }
1055
+ finally {
1056
+ client.release();
1057
+ }
1058
+ }
1014
1059
  /**
1015
1060
  * Add a key to a type schema
1016
1061
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-semicolon/semo-cli",
3
- "version": "4.10.0",
3
+ "version": "4.11.0",
4
4
  "description": "SEMO CLI - AI Agent Orchestration Framework Installer",
5
5
  "main": "dist/index.js",
6
6
  "bin": {