@team-semicolon/semo-cli 4.0.4 → 4.1.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 +150 -13
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -772,6 +772,7 @@ program
|
|
|
772
772
|
.option("--no-gitignore", ".gitignore 수정 생략")
|
|
773
773
|
.option("--migrate", "레거시 환경 강제 마이그레이션")
|
|
774
774
|
.option("--seed-skills", "semo-system/semo-skills/ → semo.skills DB 초기 시딩")
|
|
775
|
+
.option("--credentials-gist <gistId>", "Private GitHub Gist에서 팀 DB 접속정보 자동 가져오기")
|
|
775
776
|
.action(async (options) => {
|
|
776
777
|
console.log(chalk_1.default.cyan.bold("\n🚀 SEMO 설치 시작\n"));
|
|
777
778
|
console.log(chalk_1.default.gray("Gemini 하이브리드 전략: White Box + Black Box\n"));
|
|
@@ -822,6 +823,8 @@ program
|
|
|
822
823
|
}
|
|
823
824
|
// 7. Hooks 설치 (대화 로깅)
|
|
824
825
|
await setupHooks(cwd, false);
|
|
826
|
+
// 7.5. ~/.semo.env DB 접속 설정 (자동 감지 → Gist → 프롬프트)
|
|
827
|
+
await setupSemoEnv(options.credentialsGist);
|
|
825
828
|
// 8. CLAUDE.md 생성
|
|
826
829
|
await setupClaudeMd(cwd, [], options.force);
|
|
827
830
|
// 9. 설치 검증
|
|
@@ -847,10 +850,12 @@ program
|
|
|
847
850
|
console.log(chalk_1.default.gray(" ✓ semo-agents (14개 페르소나 Agent)"));
|
|
848
851
|
console.log(chalk_1.default.gray(" ✓ semo-scripts (자동화 스크립트)"));
|
|
849
852
|
console.log(chalk_1.default.cyan("\n다음 단계:"));
|
|
850
|
-
console.log(chalk_1.default.gray(" 1. Claude Code에서 프로젝트 열기"));
|
|
853
|
+
console.log(chalk_1.default.gray(" 1. Claude Code에서 프로젝트 열기 (SessionStart 훅이 자동 sync)"));
|
|
851
854
|
console.log(chalk_1.default.gray(" 2. 자연어로 요청하기 (예: \"댓글 기능 구현해줘\")"));
|
|
852
855
|
console.log(chalk_1.default.gray(" 3. /SEMO:help로 도움말 확인"));
|
|
853
856
|
console.log();
|
|
857
|
+
console.log(chalk_1.default.gray(" DB 접속정보 변경: nano ~/.semo.env"));
|
|
858
|
+
console.log();
|
|
854
859
|
});
|
|
855
860
|
// === Standard 설치 (DB 기반) ===
|
|
856
861
|
async function setupStandard(cwd, force) {
|
|
@@ -1360,16 +1365,6 @@ function printVerificationResult(result) {
|
|
|
1360
1365
|
}
|
|
1361
1366
|
}
|
|
1362
1367
|
const BASE_MCP_SERVERS = [
|
|
1363
|
-
{
|
|
1364
|
-
name: "semo-integrations",
|
|
1365
|
-
command: "npx",
|
|
1366
|
-
args: ["-y", "@team-semicolon/semo-mcp"],
|
|
1367
|
-
env: {
|
|
1368
|
-
// Slack/GitHub/DB 토큰은 패키지에 암호화 포함됨 (설정 불필요)
|
|
1369
|
-
SUPABASE_URL: "${SUPABASE_URL}",
|
|
1370
|
-
SUPABASE_KEY: "${SUPABASE_KEY}",
|
|
1371
|
-
},
|
|
1372
|
-
},
|
|
1373
1368
|
{
|
|
1374
1369
|
name: "context7",
|
|
1375
1370
|
command: "npx",
|
|
@@ -1391,6 +1386,95 @@ const BASE_MCP_SERVERS = [
|
|
|
1391
1386
|
args: ["-y", "@modelcontextprotocol/server-github"],
|
|
1392
1387
|
},
|
|
1393
1388
|
];
|
|
1389
|
+
// === ~/.semo.env 설정 (자동 감지 → Gist → 프롬프트) ===
|
|
1390
|
+
function writeSemoEnvFile(dbUrl, slackWebhook = "") {
|
|
1391
|
+
const envFile = path.join(os.homedir(), ".semo.env");
|
|
1392
|
+
const content = `# SEMO 환경변수 — 모든 컨텍스트에서 자동 로드됨
|
|
1393
|
+
# (Claude Code 앱, OpenClaw LaunchAgent, cron 등 비인터랙티브 환경 포함)
|
|
1394
|
+
|
|
1395
|
+
DATABASE_URL='${dbUrl}'
|
|
1396
|
+
|
|
1397
|
+
# Slack 알림 Webhook (선택 — bot-ops 채널 dead-letter 감지용)
|
|
1398
|
+
SLACK_WEBHOOK='${slackWebhook}'
|
|
1399
|
+
`;
|
|
1400
|
+
fs.writeFileSync(envFile, content, { mode: 0o600 });
|
|
1401
|
+
}
|
|
1402
|
+
function readSemoEnvDbUrl() {
|
|
1403
|
+
const envFile = path.join(os.homedir(), ".semo.env");
|
|
1404
|
+
if (!fs.existsSync(envFile))
|
|
1405
|
+
return null;
|
|
1406
|
+
const content = fs.readFileSync(envFile, "utf-8");
|
|
1407
|
+
const match = content.match(/^DATABASE_URL='([^']+)'/m) || content.match(/^DATABASE_URL="([^"]+)"/m);
|
|
1408
|
+
return match ? match[1] : null;
|
|
1409
|
+
}
|
|
1410
|
+
function fetchDbUrlFromGist(gistId) {
|
|
1411
|
+
try {
|
|
1412
|
+
const raw = (0, child_process_1.execSync)(`gh gist view ${gistId} --raw`, {
|
|
1413
|
+
encoding: "utf-8",
|
|
1414
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1415
|
+
timeout: 8000,
|
|
1416
|
+
});
|
|
1417
|
+
// Gist 파일 형식: DATABASE_URL=<url> 또는 단순 URL
|
|
1418
|
+
const match = raw.match(/DATABASE_URL=['"]?([^'"\s]+)['"]?/) || raw.match(/^(postgres(?:ql)?:\/\/[^\s]+)/m);
|
|
1419
|
+
return match ? match[1].trim() : null;
|
|
1420
|
+
}
|
|
1421
|
+
catch {
|
|
1422
|
+
return null;
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
async function setupSemoEnv(credentialsGist) {
|
|
1426
|
+
const envFile = path.join(os.homedir(), ".semo.env");
|
|
1427
|
+
console.log(chalk_1.default.cyan("\n🔑 DB 접속 설정"));
|
|
1428
|
+
// 1. 이미 env var로 설정됨
|
|
1429
|
+
if (process.env.DATABASE_URL) {
|
|
1430
|
+
console.log(chalk_1.default.green(" ✅ DATABASE_URL (환경변수)"));
|
|
1431
|
+
if (!readSemoEnvDbUrl()) {
|
|
1432
|
+
writeSemoEnvFile(process.env.DATABASE_URL);
|
|
1433
|
+
console.log(chalk_1.default.gray(` → ~/.semo.env 에 저장됨 (비인터랙티브 환경용)`));
|
|
1434
|
+
}
|
|
1435
|
+
return;
|
|
1436
|
+
}
|
|
1437
|
+
// 2. ~/.semo.env 에 이미 있음
|
|
1438
|
+
const existing = readSemoEnvDbUrl();
|
|
1439
|
+
if (existing) {
|
|
1440
|
+
console.log(chalk_1.default.green(" ✅ DATABASE_URL (~/.semo.env)"));
|
|
1441
|
+
return;
|
|
1442
|
+
}
|
|
1443
|
+
// 3. Private GitHub Gist 자동 fetch
|
|
1444
|
+
const gistId = credentialsGist || process.env.SEMO_CREDENTIALS_GIST;
|
|
1445
|
+
if (gistId) {
|
|
1446
|
+
console.log(chalk_1.default.gray(" GitHub Gist에서 팀 접속정보 가져오는 중..."));
|
|
1447
|
+
const url = fetchDbUrlFromGist(gistId);
|
|
1448
|
+
if (url) {
|
|
1449
|
+
writeSemoEnvFile(url);
|
|
1450
|
+
console.log(chalk_1.default.green(` ✅ Gist (${gistId.slice(0, 8)}...)에서 DATABASE_URL 설정됨`));
|
|
1451
|
+
return;
|
|
1452
|
+
}
|
|
1453
|
+
console.log(chalk_1.default.yellow(" ⚠️ Gist fetch 실패 — 수동 입력으로 전환"));
|
|
1454
|
+
}
|
|
1455
|
+
// 4. 인터랙티브 프롬프트
|
|
1456
|
+
console.log(chalk_1.default.gray(" 팀 Core DB URL을 붙여넣으세요 (나중에 ~/.semo.env에서 수정 가능)"));
|
|
1457
|
+
const { dbUrl } = await inquirer_1.default.prompt([
|
|
1458
|
+
{
|
|
1459
|
+
type: "password",
|
|
1460
|
+
name: "dbUrl",
|
|
1461
|
+
message: "DATABASE_URL:",
|
|
1462
|
+
mask: "*",
|
|
1463
|
+
},
|
|
1464
|
+
]);
|
|
1465
|
+
if (dbUrl && dbUrl.trim()) {
|
|
1466
|
+
writeSemoEnvFile(dbUrl.trim());
|
|
1467
|
+
console.log(chalk_1.default.green(" ✅ ~/.semo.env 저장됨 (권한: 600)"));
|
|
1468
|
+
console.log(chalk_1.default.gray(` 파일: ${envFile}`));
|
|
1469
|
+
}
|
|
1470
|
+
else {
|
|
1471
|
+
// 빈 템플릿 생성
|
|
1472
|
+
if (!fs.existsSync(envFile)) {
|
|
1473
|
+
writeSemoEnvFile("");
|
|
1474
|
+
}
|
|
1475
|
+
console.log(chalk_1.default.yellow(" ⚠️ 건너뜀 — ~/.semo.env에서 DATABASE_URL을 직접 입력하세요"));
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1394
1478
|
// === Claude MCP 서버 존재 여부 확인 ===
|
|
1395
1479
|
function isMCPServerRegistered(serverName) {
|
|
1396
1480
|
try {
|
|
@@ -1597,7 +1681,7 @@ async function setupHooks(cwd, isUpdate = false) {
|
|
|
1597
1681
|
hooks: [
|
|
1598
1682
|
{
|
|
1599
1683
|
type: "command",
|
|
1600
|
-
command: "semo context sync 2>/dev/null || true",
|
|
1684
|
+
command: ". ~/.semo.env 2>/dev/null; semo context sync 2>/dev/null || true",
|
|
1601
1685
|
timeout: 30,
|
|
1602
1686
|
},
|
|
1603
1687
|
],
|
|
@@ -1631,7 +1715,7 @@ async function setupHooks(cwd, isUpdate = false) {
|
|
|
1631
1715
|
hooks: [
|
|
1632
1716
|
{
|
|
1633
1717
|
type: "command",
|
|
1634
|
-
command: "semo context push 2>/dev/null || true",
|
|
1718
|
+
command: ". ~/.semo.env 2>/dev/null; semo context push 2>/dev/null || true",
|
|
1635
1719
|
timeout: 30,
|
|
1636
1720
|
},
|
|
1637
1721
|
],
|
|
@@ -2289,6 +2373,59 @@ program
|
|
|
2289
2373
|
console.log(chalk_1.default.cyan("\n새 환경 설치를 위해 'semo init'을 실행하세요.\n"));
|
|
2290
2374
|
}
|
|
2291
2375
|
});
|
|
2376
|
+
// === config 명령어 (설치 후 설정 변경) ===
|
|
2377
|
+
const configCmd = program.command("config").description("SEMO 설정 관리");
|
|
2378
|
+
configCmd
|
|
2379
|
+
.command("db")
|
|
2380
|
+
.description("팀 Core DB 접속정보 설정 (DATABASE_URL → ~/.semo.env)")
|
|
2381
|
+
.option("--credentials-gist <gistId>", "Private GitHub Gist에서 자동 가져오기")
|
|
2382
|
+
.action(async (options) => {
|
|
2383
|
+
console.log(chalk_1.default.cyan.bold("\n🔑 DB 접속정보 설정\n"));
|
|
2384
|
+
// 기존 값 확인
|
|
2385
|
+
const existing = readSemoEnvDbUrl();
|
|
2386
|
+
if (existing) {
|
|
2387
|
+
const { overwrite } = await inquirer_1.default.prompt([
|
|
2388
|
+
{
|
|
2389
|
+
type: "confirm",
|
|
2390
|
+
name: "overwrite",
|
|
2391
|
+
message: `기존 DATABASE_URL이 있습니다. 덮어쓰시겠습니까?`,
|
|
2392
|
+
default: false,
|
|
2393
|
+
},
|
|
2394
|
+
]);
|
|
2395
|
+
if (!overwrite) {
|
|
2396
|
+
console.log(chalk_1.default.gray("취소됨"));
|
|
2397
|
+
return;
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
// Gist 또는 프롬프트로 가져오기
|
|
2401
|
+
const gistId = options.credentialsGist || process.env.SEMO_CREDENTIALS_GIST;
|
|
2402
|
+
if (gistId) {
|
|
2403
|
+
console.log(chalk_1.default.gray("GitHub Gist에서 접속정보 가져오는 중..."));
|
|
2404
|
+
const url = fetchDbUrlFromGist(gistId);
|
|
2405
|
+
if (url) {
|
|
2406
|
+
writeSemoEnvFile(url);
|
|
2407
|
+
console.log(chalk_1.default.green("✅ ~/.semo.env 업데이트 완료"));
|
|
2408
|
+
return;
|
|
2409
|
+
}
|
|
2410
|
+
console.log(chalk_1.default.yellow("⚠️ Gist fetch 실패 — 수동 입력"));
|
|
2411
|
+
}
|
|
2412
|
+
const { dbUrl } = await inquirer_1.default.prompt([
|
|
2413
|
+
{
|
|
2414
|
+
type: "password",
|
|
2415
|
+
name: "dbUrl",
|
|
2416
|
+
message: "DATABASE_URL:",
|
|
2417
|
+
mask: "*",
|
|
2418
|
+
},
|
|
2419
|
+
]);
|
|
2420
|
+
if (dbUrl && dbUrl.trim()) {
|
|
2421
|
+
writeSemoEnvFile(dbUrl.trim());
|
|
2422
|
+
console.log(chalk_1.default.green("✅ ~/.semo.env 저장됨 (권한: 600)"));
|
|
2423
|
+
console.log(chalk_1.default.gray(" 다음 Claude Code 세션부터 자동으로 적용됩니다."));
|
|
2424
|
+
}
|
|
2425
|
+
else {
|
|
2426
|
+
console.log(chalk_1.default.yellow("취소됨"));
|
|
2427
|
+
}
|
|
2428
|
+
});
|
|
2292
2429
|
// === doctor 명령어 (설치 상태 진단) ===
|
|
2293
2430
|
program
|
|
2294
2431
|
.command("doctor")
|