relayax-cli 0.3.59 → 0.3.62

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.
@@ -213,12 +213,18 @@ function syncContentsToRelay(contents, contentsDiff, relayDir, projectPath) {
213
213
  continue;
214
214
  const absFrom = resolveFromPath(content.from, projectPath);
215
215
  const relaySubPath = deriveRelaySubPath(content);
216
- const relayItemDir = path_1.default.join(relayDir, relaySubPath);
217
- // 소스 파일을 .relay/로 복사
216
+ const relayTarget = path_1.default.join(relayDir, relaySubPath);
217
+ // 단일 파일인 경우 직접 복사 (디렉토리 기반 diff/sync 불필요)
218
+ if (fs_1.default.existsSync(absFrom) && fs_1.default.statSync(absFrom).isFile()) {
219
+ fs_1.default.mkdirSync(path_1.default.dirname(relayTarget), { recursive: true });
220
+ fs_1.default.copyFileSync(absFrom, relayTarget);
221
+ continue;
222
+ }
223
+ // 디렉토리인 경우 diff 기반 동기화
218
224
  const sourceFiles = scanPath(absFrom);
219
- const relayFiles = scanPath(relayItemDir);
225
+ const relayFiles = scanPath(relayTarget);
220
226
  const fileDiff = computeDiff(sourceFiles, relayFiles);
221
- syncToRelay(absFrom, relayItemDir, fileDiff);
227
+ syncToRelay(absFrom, relayTarget, fileDiff);
222
228
  }
223
229
  }
224
230
  // ─── Global Agent Home ───
@@ -23,18 +23,18 @@ const js_yaml_1 = __importDefault(require("js-yaml"));
23
23
  // eslint-disable-next-line @typescript-eslint/no-var-requires
24
24
  const pkg = require('../../package.json');
25
25
  // ─── Helpers ───
26
- async function resolveUsername(token) {
26
+ async function resolveUserInfo(token) {
27
27
  try {
28
28
  const res = await fetch(`${config_js_1.API_URL}/api/auth/me`, {
29
29
  headers: { Authorization: `Bearer ${token}` },
30
30
  });
31
31
  if (!res.ok)
32
- return undefined;
32
+ return {};
33
33
  const body = await res.json();
34
- return body.username;
34
+ return { username: body.username, email: body.email };
35
35
  }
36
36
  catch {
37
- return undefined;
37
+ return {};
38
38
  }
39
39
  }
40
40
  function countFiles(dir) {
@@ -208,8 +208,12 @@ function createMcpServer() {
208
208
  const projectPath = resolveMcpProjectPath(project_path);
209
209
  const token = await (0, config_js_1.getValidToken)();
210
210
  let username;
211
- if (token)
212
- username = await resolveUsername(token);
211
+ let email;
212
+ if (token) {
213
+ const info = await resolveUserInfo(token);
214
+ username = info.username;
215
+ email = info.email;
216
+ }
213
217
  const detected = (0, ai_tools_js_1.detectAgentCLIs)(projectPath);
214
218
  const mounted = (0, ai_tools_js_1.detectMountedCLIs)();
215
219
  const relayYaml = path_1.default.join(projectPath, '.relay', 'relay.yaml');
@@ -226,7 +230,7 @@ function createMcpServer() {
226
230
  const cliUpdate = await checkCliVersion(true);
227
231
  return { content: [jsonText({
228
232
  cli: { version: pkg.version, update_available: cliUpdate ? cliUpdate.latest : null },
229
- login: { authenticated: !!token, username },
233
+ login: { authenticated: !!token, username, email },
230
234
  agent_clis: detected.map((t) => t.name),
231
235
  mounted_paths: mounted.map((m) => m.basePath),
232
236
  project,
@@ -624,8 +628,8 @@ function createMcpServer() {
624
628
  // 이미 로그인되어 있는지 확인
625
629
  const existingToken = await (0, config_js_1.getValidToken)();
626
630
  if (existingToken) {
627
- const username = await resolveUsername(existingToken);
628
- return { content: [jsonText({ status: 'already_authenticated', username })] };
631
+ const { username, email } = await resolveUserInfo(existingToken);
632
+ return { content: [jsonText({ status: 'already_authenticated', username, email })] };
629
633
  }
630
634
  // Device code 발급
631
635
  const res = await fetch(`${config_js_1.API_URL}/api/auth/device/request`, { method: 'POST' });
@@ -663,8 +667,8 @@ function createMcpServer() {
663
667
  refresh_token: data.refresh_token,
664
668
  expires_at: data.expires_at ? Number(data.expires_at) : undefined,
665
669
  });
666
- const username = await resolveUsername(data.token);
667
- return { content: [jsonText({ status: 'ok', message: '로그인 완료', username })] };
670
+ const { username, email } = await resolveUserInfo(data.token);
671
+ return { content: [jsonText({ status: 'ok', message: '로그인 완료', username, email })] };
668
672
  }
669
673
  }
670
674
  return { content: [jsonText({ status: 'timeout', verification_url, user_code, message: `브라우저에서 ${verification_url} 을 열고 코드 ${user_code} 를 입력해주세요.` })], isError: true };
@@ -291,7 +291,11 @@ Organization 목록을 조회합니다:
291
291
  - question: "Organization이 없습니다. 비공개 배포를 하려면 Organization이 필요합니다. Organization을 만들까요?"
292
292
  - options: `["Organization 생성", "Organization 없이 계속 (공개/링크공유만 가능)"]`
293
293
  - "Organization 생성" 선택 시:
294
- - **사용자 질문 도구 호출:** question: "Organization 이름을 입력하세요."
294
+ - 로그인 정보(username, email)를 기반으로 Organization 이름을 추천합니다:
295
+ - 업무용 이메일(커스텀 도메인)이면 → 도메인에서 회사명 추출하여 추천. 예: `haemin@relayax.com` → "relayax"
296
+ - 비업무용 이메일(gmail.com, naver.com, kakao.com, daum.net, hotmail.com, outlook.com, yahoo.com, icloud.com 등 무료 메일)이면 → username을 추천. 예: `haemin` → "haemin"
297
+ - email이 없으면 → username을 추천
298
+ - **사용자 질문 도구 호출:** question: "Organization 이름을 입력하세요. (추천: {추천이름})"
295
299
  - 환경 A: `relay orgs create "이름" --json` 실행
296
300
  - 환경 B: `relay_org_create` MCP tool 호출
297
301
  - 생성 후 org 목록을 갱신합니다.
@@ -341,12 +345,21 @@ Org가 없는 경우 (개인 배포):
341
345
 
342
346
  .relay/ 내 모든 파일을 자동 분석합니다.
343
347
 
344
- #### 2-1. 시크릿 스캔 (자동)
348
+ #### 2-1. 시크릿 & 개인정보 스캔 (자동)
349
+
350
+ **시크릿 스캔:**
345
351
  - 하드코딩된 API 키, 토큰, 비밀번호, Private Key 등을 탐색합니다.
346
352
  - 예: sk-..., ghp_..., AKIA..., Bearer 토큰, JWT, -----BEGIN PRIVATE KEY----- 등
347
353
  - 발견 시 **즉시 사용자에게 경고**하고, 환경변수로 대체하도록 안내합니다.
348
354
  - 시크릿이 제거되지 않으면 배포를 진행하지 않습니다.
349
355
 
356
+ **개인정보 스캔:**
357
+ - 이메일 주소, 전화번호, 실명, 주소 등 개인정보가 포함된 파일을 탐색합니다.
358
+ - **중요: 패키지에 포함된 모든 파일은 설치한 사람이 볼 수 있습니다.** 상세페이지에 노출되지 않더라도 패키지 자체에 포함됩니다.
359
+ - 발견 시 사용자에게 경고하고 제거/수정 여부를 확인받습니다:
360
+ - "⚠ {파일명}에 개인정보({종류})가 포함되어 있습니다. 이 파일은 패키지에 포함되어 설치한 사람이 볼 수 있습니다."
361
+ - **사용자 질문 도구 호출:** question: "개인정보가 포함된 파일을 어떻게 처리할까요?", options: `["제거 후 배포", "그대로 배포", "취소"]`
362
+
350
363
  #### 2-2. 환경변수 & 의존성 분석 (자동)
351
364
  분석 대상:
352
365
  - **env**: 환경변수 참조 (process.env.*, ${VAR}, os.environ 등)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relayax-cli",
3
- "version": "0.3.59",
3
+ "version": "0.3.62",
4
4
  "description": "RelayAX Agent Team Marketplace CLI - Install and manage agent teams",
5
5
  "main": "dist/index.js",
6
6
  "bin": {