@team-semicolon/semo-cli 4.9.0 → 4.10.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/database.js CHANGED
@@ -68,11 +68,8 @@ const env_parser_1 = require("./env-parser");
68
68
  // ~/.claude/semo/.env 자동 로드 — LaunchAgent / Claude Code 앱 / cron 등
69
69
  // 인터랙티브 쉘이 아닌 환경에서 환경변수를 공급한다.
70
70
  // 이미 설정된 환경변수는 덮어쓰지 않는다 (env var > file).
71
- // v4.5.0: ~/.semo.env → ~/.claude/semo/.env 이전. 구 경로 폴백 유지.
72
71
  function loadSemoEnv() {
73
- const newEnvFile = path.join(os.homedir(), ".claude", "semo", ".env");
74
- const legacyEnvFile = path.join(os.homedir(), ".semo.env");
75
- const envFile = fs.existsSync(newEnvFile) ? newEnvFile : legacyEnvFile;
72
+ const envFile = path.join(os.homedir(), ".claude", "semo", ".env");
76
73
  if (!fs.existsSync(envFile))
77
74
  return;
78
75
  try {
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * 공용 KEY=VALUE 파서
3
- * ~/.semo.env 파일과 GitHub Gist 콘텐츠 모두 이 함수로 파싱한다.
3
+ * ~/.claude/semo/.env 파일과 GitHub Gist 콘텐츠 모두 이 함수로 파싱한다.
4
4
  */
5
5
  export declare function parseEnvContent(content: string): Record<string, string>;
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseEnvContent = parseEnvContent;
4
4
  /**
5
5
  * 공용 KEY=VALUE 파서
6
- * ~/.semo.env 파일과 GitHub Gist 콘텐츠 모두 이 함수로 파싱한다.
6
+ * ~/.claude/semo/.env 파일과 GitHub Gist 콘텐츠 모두 이 함수로 파싱한다.
7
7
  */
8
8
  function parseEnvContent(content) {
9
9
  const result = {};
package/dist/index.js CHANGED
@@ -331,8 +331,7 @@ async function showToolsStatus() {
331
331
  // === 글로벌 설정 체크 ===
332
332
  function isGlobalSetupDone() {
333
333
  const home = os.homedir();
334
- const hasEnv = fs.existsSync(path.join(home, ".claude", "semo", ".env")) ||
335
- fs.existsSync(path.join(home, ".semo.env")); // 하위 호환
334
+ const hasEnv = fs.existsSync(path.join(home, ".claude", "semo", ".env"));
336
335
  const hasSetup = fs.existsSync(path.join(home, ".claude", "semo", "SOUL.md")) ||
337
336
  fs.existsSync(path.join(home, ".claude", "skills")); // 하위 호환
338
337
  return hasEnv && hasSetup;
@@ -494,9 +493,7 @@ const BASE_MCP_SERVERS = [
494
493
  },
495
494
  ];
496
495
  // === ~/.claude/semo/.env 설정 (자동 감지 → Gist → 프롬프트) ===
497
- // v4.5.0: ~/.semo.env → ~/.claude/semo/.env 이전
498
496
  const SEMO_ENV_PATH = path.join(os.homedir(), ".claude", "semo", ".env");
499
- const LEGACY_ENV_PATH = path.join(os.homedir(), ".semo.env");
500
497
  const SEMO_CREDENTIALS = [
501
498
  {
502
499
  key: "DATABASE_URL",
@@ -544,27 +541,9 @@ function writeSemoEnvFile(creds) {
544
541
  }
545
542
  lines.push("");
546
543
  fs.writeFileSync(SEMO_ENV_PATH, lines.join("\n"), { mode: 0o600 });
547
- // 하위 호환 심링크: ~/.semo.env → ~/.claude/semo/.env
548
- try {
549
- if (fs.existsSync(LEGACY_ENV_PATH)) {
550
- const stat = fs.lstatSync(LEGACY_ENV_PATH);
551
- if (!stat.isSymbolicLink()) {
552
- // 기존 실파일은 백업 후 심링크로 교체
553
- fs.renameSync(LEGACY_ENV_PATH, LEGACY_ENV_PATH + ".bak");
554
- }
555
- else {
556
- fs.unlinkSync(LEGACY_ENV_PATH);
557
- }
558
- }
559
- fs.symlinkSync(SEMO_ENV_PATH, LEGACY_ENV_PATH);
560
- }
561
- catch {
562
- // 심링크 실패 시 무시 — 새 경로가 원본
563
- }
564
544
  }
565
545
  function readSemoEnvCreds() {
566
- // 경로 우선, 없으면 레거시 폴백
567
- const envFile = fs.existsSync(SEMO_ENV_PATH) ? SEMO_ENV_PATH : LEGACY_ENV_PATH;
546
+ const envFile = SEMO_ENV_PATH;
568
547
  if (!fs.existsSync(envFile))
569
548
  return {};
570
549
  try {
@@ -740,7 +719,7 @@ async function buildKbFirstBlock() {
740
719
  const label = s.desc || s.scheme_key;
741
720
  domainGuide += `- 서비스 ${label} → \`domain: {서비스명}\`, key: \`${s.scheme_key}\`\n`;
742
721
  }
743
- domainGuide += `- 서비스 KPI → \`domain: {서비스명}\`, key: \`kpi/current\`\n`;
722
+ domainGuide += `- 서비스 KPI → \`domain: {서비스명}\`, key: \`kpi/{YYYY-MM-DD}\` (최신: 가장 최근 날짜)\n`;
744
723
  domainGuide += `- 서비스 마일스톤 → \`domain: {서비스명}\`, key: \`milestone/{slug}\`\n`;
745
724
  domainGuide += `\n**정확한 경로를 모를 때:**\n`;
746
725
  domainGuide += `1. \`semo kb search "검색어"\` → 결과의 \`[domain] key/sub_key\` 경로 확인\n`;
@@ -853,7 +832,7 @@ async function setupHooks(isUpdate = false) {
853
832
  hooks: [
854
833
  {
855
834
  type: "command",
856
- command: ". ~/.claude/semo/.env 2>/dev/null || . ~/.semo.env 2>/dev/null; semo context sync 2>/dev/null || true",
835
+ command: ". ~/.claude/semo/.env 2>/dev/null; semo context sync 2>/dev/null || true",
857
836
  timeout: 30,
858
837
  },
859
838
  ],
@@ -865,7 +844,7 @@ async function setupHooks(isUpdate = false) {
865
844
  hooks: [
866
845
  {
867
846
  type: "command",
868
- command: ". ~/.claude/semo/.env 2>/dev/null || . ~/.semo.env 2>/dev/null; semo context push 2>/dev/null || true",
847
+ command: ". ~/.claude/semo/.env 2>/dev/null; semo context push 2>/dev/null || true",
869
848
  timeout: 30,
870
849
  },
871
850
  ],
@@ -1119,9 +1098,9 @@ npm run build # 빌드 검증
1119
1098
 
1120
1099
  ---
1121
1100
 
1122
- ## 환경변수 (\`~/.semo.env\`)
1101
+ ## 환경변수 (\`~/.claude/semo/.env\`)
1123
1102
 
1124
- SEMO는 \`~/.semo.env\` 파일에서 팀 공통 환경변수를 로드합니다.
1103
+ SEMO는 \`~/.claude/semo/.env\` 파일에서 팀 공통 환경변수를 로드합니다.
1125
1104
  SessionStart 훅과 OpenClaw 게이트웨이 래퍼에서 자동 source됩니다.
1126
1105
 
1127
1106
  | 변수 | 용도 | 필수 |
@@ -1130,7 +1109,7 @@ SessionStart 훅과 OpenClaw 게이트웨이 래퍼에서 자동 source됩니다
1130
1109
  | \`OPENAI_API_KEY\` | KB 임베딩용 (text-embedding-3-small) | 선택 |
1131
1110
  | \`SLACK_WEBHOOK\` | Slack 알림 | 선택 |
1132
1111
 
1133
- 키 갱신이 필요하면 \`~/.semo.env\`를 직접 편집하거나 \`semo onboarding -f\`를 실행하세요.
1112
+ 키 갱신이 필요하면 \`~/.claude/semo/.env\`를 직접 편집하거나 \`semo onboarding -f\`를 실행하세요.
1134
1113
 
1135
1114
  ---
1136
1115
 
@@ -1754,7 +1733,7 @@ kbCmd
1754
1733
  kbCmd
1755
1734
  .command("ontology")
1756
1735
  .description("온톨로지 조회 — 도메인/타입/스키마/라우팅 테이블")
1757
- .option("--action <type>", "동작 (list|show|services|types|instances|schema|routing-table|register|add-key|remove-key)", "list")
1736
+ .option("--action <type>", "동작 (list|show|services|types|instances|schema|routing-table|register|create-type|add-key|remove-key)", "list")
1758
1737
  .option("--domain <name>", "action=show|register 시 도메인")
1759
1738
  .option("--type <name>", "action=schema|register|add-key|remove-key 시 타입 키")
1760
1739
  .option("--key <name>", "action=add-key|remove-key 시 스키마 키")
@@ -1949,6 +1928,24 @@ kbCmd
1949
1928
  }
1950
1929
  }
1951
1930
  }
1931
+ else if (action === "create-type") {
1932
+ if (!options.type) {
1933
+ console.log(chalk_1.default.red("--type 옵션이 필요합니다. (예: --type project)"));
1934
+ process.exit(1);
1935
+ }
1936
+ const result = await (0, kb_1.ontoCreateType)(pool, {
1937
+ type_key: options.type,
1938
+ description: options.description,
1939
+ });
1940
+ if (result.success) {
1941
+ console.log(chalk_1.default.green(`\n✅ 온톨로지 타입 '${options.type}' 생성 완료`));
1942
+ console.log(chalk_1.default.gray(`\n 다음 단계: semo kb ontology --action add-key --type ${options.type} --key <key> --key-type singleton|collection\n`));
1943
+ }
1944
+ else {
1945
+ console.log(chalk_1.default.red(`\n❌ 타입 생성 실패: ${result.error}\n`));
1946
+ process.exit(1);
1947
+ }
1948
+ }
1952
1949
  else if (action === "add-key") {
1953
1950
  if (!options.type) {
1954
1951
  console.log(chalk_1.default.red("--type 옵션이 필요합니다. (예: --type service)"));
@@ -1993,7 +1990,7 @@ kbCmd
1993
1990
  }
1994
1991
  }
1995
1992
  else {
1996
- console.log(chalk_1.default.red(`알 수 없는 action: '${action}'. 사용 가능: list, show, services, types, instances, schema, routing-table, register, add-key, remove-key`));
1993
+ console.log(chalk_1.default.red(`알 수 없는 action: '${action}'. 사용 가능: list, show, services, types, instances, schema, routing-table, register, create-type, add-key, remove-key`));
1997
1994
  process.exit(1);
1998
1995
  }
1999
1996
  await (0, database_1.closeConnection)();
package/dist/kb.d.ts CHANGED
@@ -246,6 +246,17 @@ export declare function ontoAddKey(pool: Pool, opts: {
246
246
  success: boolean;
247
247
  error?: string;
248
248
  }>;
249
+ /**
250
+ * Create a new ontology type
251
+ */
252
+ export declare function ontoCreateType(pool: Pool, opts: {
253
+ type_key: string;
254
+ description?: string;
255
+ schema?: Record<string, unknown>;
256
+ }): Promise<{
257
+ success: boolean;
258
+ error?: string;
259
+ }>;
249
260
  /**
250
261
  * Remove a key from a type schema
251
262
  */
package/dist/kb.js CHANGED
@@ -63,6 +63,7 @@ exports.ontoListServices = ontoListServices;
63
63
  exports.ontoListInstances = ontoListInstances;
64
64
  exports.ontoRegister = ontoRegister;
65
65
  exports.ontoAddKey = ontoAddKey;
66
+ exports.ontoCreateType = ontoCreateType;
66
67
  exports.ontoRemoveKey = ontoRemoveKey;
67
68
  exports.ontoPullToLocal = ontoPullToLocal;
68
69
  const fs = __importStar(require("fs"));
@@ -1057,6 +1058,36 @@ async function ontoAddKey(pool, opts) {
1057
1058
  client.release();
1058
1059
  }
1059
1060
  }
1061
+ /**
1062
+ * Create a new ontology type
1063
+ */
1064
+ async function ontoCreateType(pool, opts) {
1065
+ // kebab-case 검증
1066
+ if (!/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(opts.type_key)) {
1067
+ return { success: false, error: `타입 키 '${opts.type_key}'이(가) 유효하지 않습니다. kebab-case 소문자만 사용 가능 (예: my-type)` };
1068
+ }
1069
+ const client = await pool.connect();
1070
+ try {
1071
+ // 중복 확인
1072
+ const dupCheck = await client.query("SELECT type_key FROM semo.ontology_types WHERE type_key = $1", [opts.type_key]);
1073
+ if (dupCheck.rows.length > 0) {
1074
+ return { success: false, error: `타입 '${opts.type_key}'은(는) 이미 존재합니다.` };
1075
+ }
1076
+ await client.query(`INSERT INTO semo.ontology_types (type_key, schema, description)
1077
+ VALUES ($1, $2, $3)`, [
1078
+ opts.type_key,
1079
+ JSON.stringify(opts.schema || {}),
1080
+ opts.description || opts.type_key,
1081
+ ]);
1082
+ return { success: true };
1083
+ }
1084
+ catch (err) {
1085
+ return { success: false, error: String(err) };
1086
+ }
1087
+ finally {
1088
+ client.release();
1089
+ }
1090
+ }
1060
1091
  /**
1061
1092
  * Remove a key from a type schema
1062
1093
  */
@@ -2,7 +2,7 @@
2
2
  * Slack 알림 유틸리티
3
3
  *
4
4
  * SLACK_WEBHOOK 환경변수에서 URL을 읽어 알림 전송.
5
- * ~/.semo.env에서 자동 로드됨 (database.ts loadSemoEnv).
5
+ * ~/.claude/semo/.env에서 자동 로드됨 (database.ts loadSemoEnv).
6
6
  */
7
7
  export declare function sendSlackNotification(message: string, webhookUrl?: string): Promise<boolean>;
8
8
  export declare function formatTestFailureMessage(suiteId: string, runId: string, pass: number, fail: number, warn: number, failedLabels: string[]): string;
@@ -3,7 +3,7 @@
3
3
  * Slack 알림 유틸리티
4
4
  *
5
5
  * SLACK_WEBHOOK 환경변수에서 URL을 읽어 알림 전송.
6
- * ~/.semo.env에서 자동 로드됨 (database.ts loadSemoEnv).
6
+ * ~/.claude/semo/.env에서 자동 로드됨 (database.ts loadSemoEnv).
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.sendSlackNotification = sendSlackNotification;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-semicolon/semo-cli",
3
- "version": "4.9.0",
3
+ "version": "4.10.0",
4
4
  "description": "SEMO CLI - AI Agent Orchestration Framework Installer",
5
5
  "main": "dist/index.js",
6
6
  "bin": {