relayax-cli 0.2.39 → 0.2.41

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.
@@ -22,7 +22,7 @@ function registerSpaces(program) {
22
22
  const token = await (0, config_js_1.getValidToken)();
23
23
  if (!token) {
24
24
  if (json) {
25
- console.error(JSON.stringify({ error: 'LOGIN_REQUIRED', message: '로그인이 필요합니다. relay login을 먼저 실행하세요.' }));
25
+ console.error(JSON.stringify({ error: 'LOGIN_REQUIRED', message: '로그인이 필요합니다. relay login을 먼저 실행하세요.', fix: 'relay login 실행 후 재시도하세요.' }));
26
26
  }
27
27
  else {
28
28
  console.error('\x1b[31m오류: 로그인이 필요합니다.\x1b[0m');
@@ -36,29 +36,21 @@ function registerSpaces(program) {
36
36
  console.log(JSON.stringify({ spaces }));
37
37
  return;
38
38
  }
39
- const personal = spaces.find((s) => s.is_personal);
40
- const others = spaces.filter((s) => !s.is_personal);
41
- if (personal) {
42
- console.log(`\n\x1b[90m개인 스페이스:\x1b[0m`);
43
- console.log(` \x1b[36m${personal.slug}\x1b[0m \x1b[1m${personal.name}\x1b[0m`);
39
+ if (spaces.length === 0) {
40
+ console.log('\nSpace가 없습니다.');
41
+ console.log('\x1b[33m💡 Space를 만들려면: www.relayax.com/spaces/new\x1b[0m');
44
42
  }
45
- if (others.length > 0) {
46
- console.log(`\n\x1b[1m내 Space\x1b[0m (${others.length}개):\n`);
47
- for (const s of others) {
43
+ else {
44
+ console.log(`\n\x1b[1m내 Space\x1b[0m (${spaces.length}개):\n`);
45
+ for (const s of spaces) {
48
46
  const role = s.role === 'owner' ? '\x1b[33m소유자\x1b[0m'
49
- : s.role === 'admin' ? '\x1b[36m관리자\x1b[0m'
47
+ : s.role === 'builder' ? '\x1b[36m빌더\x1b[0m'
50
48
  : '\x1b[90m멤버\x1b[0m';
51
49
  const desc = s.description
52
50
  ? ` \x1b[90m${s.description.length > 40 ? s.description.slice(0, 40) + '...' : s.description}\x1b[0m`
53
51
  : '';
54
52
  console.log(` \x1b[36m${s.slug}\x1b[0m \x1b[1m${s.name}\x1b[0m ${role}${desc}`);
55
53
  }
56
- }
57
- if (!personal && others.length === 0) {
58
- console.log('\nSpace가 없습니다.');
59
- console.log('\x1b[33m💡 Space를 만들려면: www.relayax.com/spaces/new\x1b[0m');
60
- }
61
- if (others.length > 0) {
62
54
  console.log(`\n\x1b[33m💡 Space 팀 목록: relay list --space <slug>\x1b[0m`);
63
55
  console.log(`\x1b[33m💡 비공개 배포: relay.yaml에 visibility: private 설정 후 relay publish\x1b[0m`);
64
56
  }
@@ -66,7 +58,7 @@ function registerSpaces(program) {
66
58
  catch (err) {
67
59
  const message = err instanceof Error ? err.message : String(err);
68
60
  if (json) {
69
- console.error(JSON.stringify({ error: 'FETCH_FAILED', message }));
61
+ console.error(JSON.stringify({ error: 'FETCH_FAILED', message, fix: '네트워크 연결을 확인하거나 잠시 후 재시도하세요.' }));
70
62
  }
71
63
  else {
72
64
  console.error(`\x1b[31m오류: ${message}\x1b[0m`);
@@ -133,7 +133,7 @@ function registerUpdate(program) {
133
133
  }
134
134
  catch (err) {
135
135
  const message = err instanceof Error ? err.message : String(err);
136
- console.error(JSON.stringify({ error: 'UPDATE_FAILED', message }));
136
+ console.error(JSON.stringify({ error: 'UPDATE_FAILED', message, fix: 'npm update -g relayax-cli로 수동 업데이트하세요.' }));
137
137
  process.exit(1);
138
138
  }
139
139
  finally {
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ const search_js_1 = require("./commands/search.js");
9
9
  const install_js_1 = require("./commands/install.js");
10
10
  const list_js_1 = require("./commands/list.js");
11
11
  const uninstall_js_1 = require("./commands/uninstall.js");
12
+ const package_js_1 = require("./commands/package.js");
12
13
  const publish_js_1 = require("./commands/publish.js");
13
14
  const login_js_1 = require("./commands/login.js");
14
15
  const update_js_1 = require("./commands/update.js");
@@ -20,6 +21,7 @@ const join_js_1 = require("./commands/join.js");
20
21
  const spaces_js_1 = require("./commands/spaces.js");
21
22
  const deploy_record_js_1 = require("./commands/deploy-record.js");
22
23
  const ping_js_1 = require("./commands/ping.js");
24
+ const access_js_1 = require("./commands/access.js");
23
25
  // eslint-disable-next-line @typescript-eslint/no-var-requires
24
26
  const pkg = require('../package.json');
25
27
  const program = new commander_1.Command();
@@ -35,6 +37,7 @@ program
35
37
  (0, install_js_1.registerInstall)(program);
36
38
  (0, list_js_1.registerList)(program);
37
39
  (0, uninstall_js_1.registerUninstall)(program);
40
+ (0, package_js_1.registerPackage)(program);
38
41
  (0, publish_js_1.registerPublish)(program);
39
42
  (0, login_js_1.registerLogin)(program);
40
43
  (0, update_js_1.registerUpdate)(program);
@@ -46,4 +49,5 @@ program
46
49
  (0, spaces_js_1.registerSpaces)(program);
47
50
  (0, deploy_record_js_1.registerDeployRecord)(program);
48
51
  (0, ping_js_1.registerPing)(program);
52
+ (0, access_js_1.registerAccess)(program);
49
53
  program.parse();
@@ -57,13 +57,46 @@ function getGlobalCommandPathForTool(skillsDir, commandId) {
57
57
  function formatCommandFile(content) {
58
58
  return `---\ndescription: ${content.description}\n---\n\n${content.body}\n`;
59
59
  }
60
- // ─── Login JIT 공통 안내 ───
61
- const LOGIN_JIT_GUIDE = `
62
- ### 인증 오류 처리
63
- - 커맨드 실행 결과에 \`LOGIN_REQUIRED\` 에러가 포함되면:
64
- 1. \`relay login\` 실행 (timeout 300초)
65
- - 브라우저가 자동으로 열리고, 사용자가 로그인을 완료하면 토큰이 자동 저장됩니다.
66
- 2. 완료 후 \`relay status --json\`으로 로그인 성공을 확인하고, 원래 커맨드를 재실행합니다.`;
60
+ // ─── 에러 처리 가이드 (결정적 실행 + 비결정적 조율) ───
61
+ const ERROR_HANDLING_GUIDE = `
62
+ ### 에러 처리 가이드
63
+
64
+ CLI 명령 실행 JSON 에러가 반환되면 아래 기준에 따라 처리합니다.
65
+ **원칙: "되돌릴 없는 영향이 있는가?"로 판단합니다.**
66
+
67
+ #### 1. 자동 해결 (사용자에게 물어보지 않음)
68
+ 되돌릴 수 있고, 부작용 없는 에러:
69
+
70
+ | 에러 코드 | 행동 |
71
+ |-----------|------|
72
+ | \`LOGIN_REQUIRED\` / \`NO_TOKEN\` | \`relay login\` 실행 (timeout 300초, 브라우저 자동 열림) → 성공 후 원래 명령 재시도 |
73
+ | \`NOT_INITIALIZED\` | \`relay init --all --json\` 실행 → 원래 명령 재시도 |
74
+ | \`FETCH_FAILED\` | 3초 대기 후 원래 명령 재시도 (최대 2회). 2회 실패 시 사용자에게 안내 |
75
+
76
+ #### 2. 사용자에게 선택지 제시 (AskUserQuestion)
77
+ \`options\` 필드가 있는 에러:
78
+
79
+ | 에러 코드 | 행동 |
80
+ |-----------|------|
81
+ | \`MISSING_VISIBILITY\` | options의 label을 선택지로 AskUserQuestion 호출 |
82
+ | \`MISSING_FIELD\` | fix 안내 + 사용자에게 값 입력 요청 |
83
+ | \`MISSING_TOOLS\` | options의 감지된 도구 목록을 선택지로 AskUserQuestion 호출 |
84
+ | \`MISSING_SPACE\` | options의 Space 목록을 선택지로 AskUserQuestion 호출 |
85
+
86
+ 사용자가 선택하면, 선택된 값을 CLI 플래그에 반영하여 명령을 재호출합니다.
87
+
88
+ #### 3. 사용자에게 안내 (되돌릴 수 없는 에러)
89
+ 구매, 접근 권한, 보안 관련:
90
+
91
+ | 에러 코드 | 행동 |
92
+ |-----------|------|
93
+ | \`GATED_ACCESS_REQUIRED\` | purchase_info의 message/url 표시 → "접근 코드가 있으신가요?" AskUserQuestion |
94
+ | \`SPACE_ONLY\` | Space 가입 필요 안내 → "초대 코드가 있으신가요?" AskUserQuestion |
95
+ | \`APPROVAL_REQUIRED\` | 승인 대기 안내 |
96
+ | \`NO_ACCESS\` | 접근 방법 안내 |
97
+
98
+ #### 4. 그 외 에러
99
+ \`fix\` 필드의 메시지를 사용자에게 전달하고, 필요하면 다음 행동을 제안합니다.`;
67
100
  // ─── 명함 표시 포맷 ───
68
101
  const BUSINESS_CARD_FORMAT = `
69
102
  ### 빌더 명함 표시
@@ -128,7 +161,7 @@ slug가 직접 주어지면 (\`/relay-install @alice/doc-writer\`) 이 단계를
128
161
 
129
162
  **AskUserQuestion 호출:**
130
163
  - question: "어디서 팀을 찾을까요?"
131
- - options: Space가 있으면 \`["<space1_name> (개인)", "<space2_name>", ...]\`, 없으면 이 단계를 건너뛰고 바로 내 Space 탐색으로 진행
164
+ - options: Space가 있으면 \`["<space1_name>", "<space2_name>", ...]\`, 없으면 이 단계를 건너뛰고 바로 내 Space 탐색으로 진행
132
165
 
133
166
  **응답 처리:**
134
167
  - Space 이름 선택 → 1-2. Space 팀 탐색으로 진행
@@ -177,13 +210,24 @@ slug가 직접 주어지면 (\`/relay-install @alice/doc-writer\`) 이 단계를
177
210
 
178
211
  #### 2-1. 패키지 다운로드
179
212
  \`relay install <@space/team> --json\` 명령어를 실행합니다.
180
- - 공개 팀: \`relay install <@space/team> --json\`
181
- - Space 팀 (비공개): \`relay install @<space-slug>/<team-slug> --json\`
213
+ - 공개 (public): \`relay install <@space/team> --json\`
214
+ - 링크 공유 팀 (gated): \`relay install <slug> --json\`
215
+ - 접근 권한이 없으면 CLI가 **purchase_info** (구매 안내 메시지 + URL)를 표시합니다.
216
+ - 접근 링크 코드가 있으면: \`relay access <slug> --code <code>\` 로 접근 부여 + 자동 설치를 한번에 수행합니다.
217
+ - Space 멤버이면 접근 확인 없이 바로 설치됩니다.
218
+ - Space 팀 (비공개/private): \`relay install @<space-slug>/<team-slug> --json\`
182
219
  - Space 가입이 필요하면: \`relay join <space-slug> --code <invite-code>\` 를 먼저 실행합니다.
183
220
  - 또는 \`--join-code <code>\`로 가입+설치를 한번에 할 수 있습니다.
184
221
  - CLI가 init과 login을 자동으로 처리합니다 (사용자가 별도 실행할 필요 없음).
185
222
  - JSON 출력에서 \`install_path\` (패키지 경로)를 확인합니다.
186
223
 
224
+ **gated 팀 접근 거부 처리:**
225
+ - CLI가 403 + \`GATED_ACCESS_REQUIRED\` 에러를 반환하면:
226
+ 1. purchase_info의 message와 url을 사용자에게 표시합니다.
227
+ 2. "접근 링크 코드가 있으신가요?"라고 물어봅니다.
228
+ 3. 코드가 있으면 \`relay access <slug> --code <code>\`를 실행합니다.
229
+ 4. 코드가 없으면 purchase_info의 url로 구매 안내합니다.
230
+
187
231
  #### 2-2. 배치 범위 선택 (추천 포함)
188
232
 
189
233
  팀의 성격을 분석하여 글로벌/로컬 중 적합한 쪽을 추천합니다.
@@ -283,7 +327,7 @@ requires 섹션이 없거나 비어있으면 이 단계를 건너뜁니다.
283
327
  \`\`\`
284
328
  ⚠️ 필수 요구사항이 충족되지 않았습니다. 팀 기능이 제한될 수 있습니다.
285
329
  \`\`\`
286
- ${LOGIN_JIT_GUIDE}
330
+ ${ERROR_HANDLING_GUIDE}
287
331
 
288
332
  ### Step 3. 완료 & 팔로우 제안
289
333
 
@@ -308,17 +352,12 @@ ${BUSINESS_CARD_FORMAT}
308
352
  \`\`\`
309
353
  주변인에게 공유하세요:
310
354
 
311
- npm install -g relayax-cli
312
- /relay:relay-install {slug}
313
-
314
- 상세페이지: relayax.com/@{owner}/{team}
355
+ 아래 가이드를 읽고 그대로 따라해줘:
356
+ https://relayax.com/api/registry/{owner}/{slug}/guide.md
315
357
  \`\`\`
316
358
 
317
- - \`{slug}\`가 \`@owner/team\` 형식이면 상세페이지는 \`relayax.com/@{owner}/{team}\`으로 치환합니다.
318
-
319
- - type 기반 사용법도 포함:
320
- - command/hybrid: \`# 사용법: /{첫번째 커맨드}\`
321
- - passive: \`# 설치하면 자동 적용됩니다\`
359
+ - \`{slug}\`가 \`@owner/team\` 형식이면 \`{owner}\`과 \`{slug}\`를 분리하여 URL을 구성합니다.
360
+ - CLI가 이미 설치된 사용자를 위한 짧은 버전도 함께 표시합니다: \`/relay:relay-install {slug}\`
322
361
 
323
362
  #### 3-4. 사용 제안
324
363
  - "바로 사용해볼까요?" 제안
@@ -332,8 +371,8 @@ npm install -g relayax-cli
332
371
 
333
372
  ### 인터랙티브 모드 (/relay-install)
334
373
  → relay spaces --json 실행
335
- → AskUserQuestion: "어디서 팀을 찾을까요?" → ["개인 Space (alice)", "Acme Corp"]
336
- → "개인 Space" 선택 → "어떤 팀을 찾고 계세요?"
374
+ → AskUserQuestion: "어디서 팀을 찾을까요?" → ["Alice's Space (alice)", "Acme Corp"]
375
+ → "Alice's Space" 선택 → "어떤 팀을 찾고 계세요?"
337
376
  → relay search "문서" 실행 → 결과 리스트 표시
338
377
  → AskUserQuestion: "어떤 팀을 설치할까요?" → ["1", "2", "3", "다시 검색"]
339
378
  → "1" 선택 (@alice/doc-writer)
@@ -400,7 +439,6 @@ npm install -g relayax-cli
400
439
  "slug": "my-space",
401
440
  "name": "내 스페이스",
402
441
  "description": "설명",
403
- "is_personal": false,
404
442
  "role": "owner"
405
443
  }
406
444
  ]
@@ -408,9 +446,8 @@ npm install -g relayax-cli
408
446
  \`\`\`
409
447
 
410
448
  **표시:**
411
- - \`is_personal: true\`개인 스페이스로 분류
412
- - \`role\`: owner → 소유자, admin → 관리자, member → 멤버
413
- ${LOGIN_JIT_GUIDE}
449
+ - \`role\`: owner소유자, builder → 빌더, member → 멤버
450
+ ${ERROR_HANDLING_GUIDE}
414
451
  - Spaces 조회 실패해도 설치된 팀 목록은 정상 표시합니다 (로컬 데이터).
415
452
 
416
453
  ### 3. Space 팀 목록 (옵션)
@@ -469,58 +506,154 @@ ${LOGIN_JIT_GUIDE}
469
506
  description: '현재 팀 패키지를 relay Space에 배포합니다',
470
507
  body: `현재 디렉토리의 에이전트 팀(.relay/)을 분석하고, 보안 점검 및 requirements를 구성한 뒤, 사용가이드를 생성하고 relay Space에 배포합니다.
471
508
 
472
- ## 사전 준비 (자동)
509
+ ## 사전 준비
510
+
511
+ ### 0-1. 인증 확인
512
+
513
+ - \`relay status --json\` 명령어를 실행하여 로그인 상태를 확인합니다.
514
+ - 인증되어 있으면 다음 단계로 진행합니다.
515
+ - 미인증이면 바로 로그인을 진행합니다:
516
+ 1. \`relay login\` 실행 (timeout 300초)
517
+ - 브라우저가 자동으로 열리고, 사용자가 로그인을 완료하면 토큰이 자동 저장됩니다.
518
+ 2. 완료 후 \`relay status --json\`으로 로그인 성공을 확인합니다.
519
+
520
+ ### 0-2. 소스 패키징 (source → .relay/)
521
+
522
+ \`relay package\` CLI 명령을 사용하여 소스 디렉토리의 콘텐츠를 .relay/로 동기화합니다.
523
+ 소스 탐색과 파일 비교는 CLI가 결정적으로 처리하고, 에이전트는 결과를 사용자에게 보여주고 흐름을 조율합니다.
524
+
525
+ #### A. 최초 배포 (.relay/relay.yaml이 없음)
526
+
527
+ ##### 1단계: 소스 탐색
528
+
529
+ \`relay package --init --json\` 실행
530
+ - CLI가 프로젝트에서 에이전트 CLI 디렉토리(.claude/, .codex/, .gemini/ 등)를 자동 탐색합니다.
531
+ - JSON 결과의 \`detected\` 배열에 각 디렉토리별 콘텐츠 요약이 포함됩니다:
532
+ \`\`\`json
533
+ {
534
+ "status": "init_required",
535
+ "detected": [
536
+ { "source": ".claude", "name": "Claude Code", "summary": { "skills": 2, "commands": 3 }, "fileCount": 8 },
537
+ { "source": ".codex", "name": "Codex", "summary": { "agents": 1 }, "fileCount": 2 }
538
+ ]
539
+ }
540
+ \`\`\`
541
+
542
+ - **detected가 0개** → "배포 가능한 에이전트 콘텐츠가 없습니다. skills/이나 commands/를 먼저 만들어주세요." 안내 후 중단
473
543
 
474
- ### 0-1. 프로젝트 확인 & 초기화
544
+ ##### 2단계: 콘텐츠 분석 & 팀 포지셔닝
475
545
 
476
- .relay/relay.yaml이 있는지 확인합니다.
546
+ detected된 소스 디렉토리의 skills/, commands/, agents/, rules/ 파일 **내용을 직접 읽어** 팀의 정체성을 파악합니다.
477
547
 
478
- **있으면** → 바로 0-2로 진행합니다.
548
+ **분석 관점:**
549
+ - 이 팀이 **무엇을 하는 팀**인지 (코드 리뷰? QA? 문서 생성? 데이터 분석?)
550
+ - 어떤 **기술 스택/도메인**에 특화되어 있는지 (Supabase? React? Python?)
551
+ - 설치자에게 **어떤 가치**를 제공하는지
479
552
 
480
- **없으면** 프로젝트를 인라인으로 초기화합니다:
553
+ 분석을 기반으로 팀을 하나의 "제품"으로 포지셔닝합니다.
481
554
 
482
- 1. "이 프로젝트를 relay 팀으로 배포하겠습니다. 먼저 정보를 입력해주세요." 안내
555
+ **중요: 소스 디렉토리 이름(.claude 등)은 인프라 디테일이므로 사용자에게 노출하지 않습니다.**
556
+ 사용자에게는 팀의 기능과 정체성 중심으로 질문합니다.
483
557
 
484
- 2. **AskUserQuestion 호출:**
485
- - question: "팀 이름을 입력해주세요"
486
- - 기본값으로 현재 디렉토리명을 제안합니다.
558
+ ##### 3단계: 배포 제안
487
559
 
488
- 3. **AskUserQuestion 호출:**
489
- - question: "팀을 한 줄로 설명해주세요 (Space에 표시됩니다)"
490
- - 기본값으로 적절한 값을 만들어줍니다.
560
+ 분석 결과를 바탕으로 팀 배포를 제안합니다.
491
561
 
492
- 4. 자동 처리:
493
- - \`mkdir -p .relay/skills .relay/commands .relay/agents .relay/rules\` 실행
494
- - \`.relay/relay.yaml\` 생성:
495
- \`\`\`yaml
496
- name: <입력된 이름>
497
- slug: <이름에서 자동 생성 소문자, 특수문자→하이픈>
498
- description: <입력된 설명>
499
- version: 1.0.0
500
- tags: []
562
+ **detected가 1개일 때:**
563
+
564
+ **AskUserQuestion 호출:**
565
+ - question: 콘텐츠 분석 기반의 포지셔닝 질문
566
+ - 예시:
567
+ - "Supabase 개발팀으로 배포할까요? (skills 2개, commands 3개)"
568
+ - "코드 리뷰 자동화 팀으로 배포할까요? (skills 1개, agents 2개)"
569
+ - "Next.js QA 테스트팀으로 배포할까요? (commands 5개)"
570
+ - options: \`["배포", "취소"]\`
571
+
572
+ **detected가 여러 개일 때:**
573
+ 각 소스의 콘텐츠를 분석하여 서로 다른 팀으로 포지셔닝합니다.
574
+
575
+ **AskUserQuestion 호출:**
576
+ - question: "어떤 팀으로 배포할까요?"
577
+ - options: 콘텐츠 기반 설명 (예: \`["Supabase 웹 개발팀 (skills 2개, commands 3개)", "QA 자동화 에이전트 (agents 1개)"]\`)
578
+ - 디렉토리 이름은 내부적으로만 매핑하고, 사용자에게는 팀의 기능으로 보여줍니다.
579
+
580
+ ##### 4단계: 팀 정보 확정
581
+
582
+ 포지셔닝 분석을 기반으로 팀 이름과 설명을 제안합니다.
583
+
584
+ **AskUserQuestion 호출:**
585
+ - question: "팀 이름을 확인해주세요"
586
+ - 분석된 포지셔닝에서 자연스러운 팀 이름을 제안합니다 (예: "supabase-web-dev", "code-reviewer")
587
+ - 현재 디렉토리명이 아닌, **콘텐츠 기반** 이름을 기본값으로 제시합니다.
588
+
589
+ **AskUserQuestion 호출:**
590
+ - question: "팀 설명을 확인해주세요 (Space에 표시됩니다)"
591
+ - 분석한 콘텐츠를 기반으로 설치자 관점의 설명을 제안합니다.
592
+ - 좋은 예: "Supabase 기반 웹앱의 DB 마이그레이션, API 개발, 테스트를 자동화합니다"
593
+ - 나쁜 예: ".claude 디렉토리의 skills와 commands를 패키징한 팀"
594
+
595
+ ##### 5단계: 초기화 & 패키징
596
+
597
+ 자동 처리:
598
+ - \`.relay/relay.yaml\` 생성:
599
+ \`\`\`yaml
600
+ name: <확정된 이름>
601
+ slug: <이름에서 자동 생성 — 소문자, 특수문자→하이픈>
602
+ description: <확정된 설명>
603
+ source: <선택된 소스 디렉토리> # 예: .claude (내부용)
604
+ version: 1.0.0
605
+ tags: []
606
+ \`\`\`
607
+ - \`relay package --source <선택된 소스> --sync --json\` 실행하여 콘텐츠를 .relay/로 복사
608
+ - \`relay init --auto\` 실행하여 글로벌 커맨드 설치 보장
609
+ - 결과를 사용자에게 표시
610
+
611
+ #### B. 재배포 (.relay/relay.yaml이 있음)
612
+
613
+ 1. \`relay package --json\` 실행
614
+ - CLI가 relay.yaml의 \`source\` 필드를 읽고, 소스 디렉토리와 .relay/를 파일 해시로 비교합니다.
615
+ - JSON 결과 예시:
616
+ \`\`\`json
617
+ {
618
+ "source": ".claude",
619
+ "sourceName": "Claude Code",
620
+ "synced": false,
621
+ "diff": [
622
+ { "relPath": "skills/code-review/SKILL.md", "status": "modified" },
623
+ { "relPath": "commands/deploy.md", "status": "added" },
624
+ { "relPath": "commands/old-cmd.md", "status": "deleted" }
625
+ ],
626
+ "summary": { "added": 1, "modified": 1, "deleted": 1, "unchanged": 5 }
627
+ }
501
628
  \`\`\`
502
- - \`relay init --update\` 실행하여 글로벌 커맨드 설치 보장
503
629
 
504
- 5. "✓ 프로젝트가 초기화되었습니다. 배포를 계속 진행합니다." 안내
630
+ 2. **변경이 있으면** (added + modified + deleted > 0) → diff를 사용자에게 보여줍니다:
631
+ \`\`\`
632
+ 📦 소스 동기화 (.claude/ → .relay/)
633
+ 변경: skills/code-review/SKILL.md
634
+ 신규: commands/deploy.md
635
+ 삭제: commands/old-cmd.md
636
+ 유지: 5개 파일
637
+ \`\`\`
505
638
 
506
- ### 0-2. 인증 확인
639
+ **AskUserQuestion 호출:**
640
+ - question: "소스 변경사항을 .relay/에 반영할까요?"
641
+ - options: \`["반영", "변경 확인", "건너뛰기"]\`
507
642
 
508
- - \`relay status --json\` 명령어를 실행하여 로그인 상태를 확인합니다.
509
- - 인증되어 있으면 다음 단계로 진행합니다.
510
- - 미인증이면 바로 로그인을 진행합니다:
511
- 1. \`relay login\` 실행 (timeout 300초)
512
- - 브라우저가 자동으로 열리고, 사용자가 로그인을 완료하면 토큰이 자동 저장됩니다.
513
- 2. 완료 \`relay status --json\`으로 로그인 성공을 확인합니다.
643
+ **응답 처리:**
644
+ - "반영" \`relay package --sync --json\` 실행하여 동기화
645
+ - "변경 확인" 변경된 파일의 내용을 직접 읽어 diff를 상세히 보여준 후 다시 AskUserQuestion
646
+ - "건너뛰기" 현재 .relay/ 그대로 배포
647
+
648
+ 3. **변경이 없으면** "✓ 소스와 동기화 상태입니다." 표시 후 다음 단계로
514
649
 
515
- ### 0-3. 구조 분석
516
- - .relay/ 디렉토리의 skills/, agents/, rules/, commands/를 탐색합니다.
517
- - 각 파일의 이름과 description을 추출합니다.
650
+ 4. \`source\` 필드가 없으면 → .relay/ 콘텐츠를 직접 편집하는 모드로 간주하고 동기화를 건너뜁니다.
518
651
 
519
652
  ## 인터랙션 플로우
520
653
 
521
- 이 커맨드는 5단계 인터랙션으로 진행됩니다. 각 단계에서 반드시 AskUserQuestion 도구를 사용하세요.
654
+ 이 커맨드는 4단계 인터랙션으로 진행됩니다. 각 단계에서 반드시 AskUserQuestion 도구를 사용하세요.
522
655
 
523
- ### Step 0. 버전 범프
656
+ ### Step 1. 버전 범프
524
657
 
525
658
  relay.yaml의 현재 \`version\`을 읽고 semver 범프를 제안합니다.
526
659
 
@@ -541,14 +674,14 @@ relay.yaml의 \`visibility\` 설정을 확인합니다.
541
674
  #### 신규 배포 (visibility 미설정)
542
675
 
543
676
  **AskUserQuestion 호출:**
544
- - question: "어디에 배포할까요?"
545
- - options: \`["공개 (Space 공개)", "비공개 (Space 전용)"]\`
677
+ - question: "공개 범위를 선택하세요"
678
+ - options: \`["공개 누구나 설치", "링크 공유 — 접근 링크가 있는 사람만 설치", "비공개 Space 멤버만"]\`
546
679
 
547
680
  **응답 처리:**
548
681
  - "공개" → relay.yaml에 \`visibility: public\` 저장
682
+ - "링크 공유" → relay.yaml에 \`visibility: gated\` 저장. 배포 후 웹 대시보드(/dashboard)에서 접근 링크를 생성하고 구매 안내를 설정할 수 있다고 안내.
549
683
  - "비공개" → \`relay spaces --json\` 실행 후 Space 목록 표시
550
- - \`is_personal: true\`인 개인 Space **제외**합니다. 비공개 배포는 Space에만 가능합니다.
551
- - 팀 Space가 0개이면: "비공개 배포하려면 팀 Space가 필요합니다. www.relayax.com/spaces 에서 Space를 생성하세요."라고 안내하고 중단합니다.
684
+ - Space가 0개이면: "비공개 배포하려면 Space 필요합니다. www.relayax.com/spaces 에서 Space 생성하세요."라고 안내하고 중단합니다.
552
685
 
553
686
  **AskUserQuestion 호출 (Space가 1개여도 반드시 호출):**
554
687
  - question: "어떤 Space에 배포할까요?"
@@ -562,7 +695,7 @@ relay.yaml의 \`visibility\` 설정을 확인합니다.
562
695
  현재 설정을 확인합니다:
563
696
 
564
697
  **AskUserQuestion 호출:**
565
- - question: 공개일 때 "현재 **공개** 설정입니다. Space에 공개됩니다. 유지할까요?", 비공개일 때 "현재 **비공개** 설정입니다 (Space: {name}). 유지할까요?"
698
+ - question: 공개일 때 "현재 **공개** 설정입니다. 유지할까요?", 링크공유일 때 "현재 **링크 공유** 설정입니다. 접근 링크가 있는 사람만 설치 가능합니다. 유지할까요?", 비공개일 때 "현재 **비공개** 설정입니다 (Space: {name}). 유지할까요?"
566
699
  - options: \`["유지", "변경"]\`
567
700
 
568
701
  "변경" → 신규 배포와 동일한 플로우
@@ -676,12 +809,13 @@ requires: env 2개, cli 1개
676
809
  - 출력에 코드블록이 없으면 아래 형태로 직접 생성합니다:
677
810
 
678
811
  \\\`\\\`\\\`
679
- npm install -g relayax-cli
680
- /relay:relay-install <slug>
812
+ 아래 가이드를 읽고 그대로 따라해줘:
813
+ https://relayax.com/api/registry/{owner}/{slug}/guide.md
681
814
  \\\`\\\`\\\`
682
815
 
683
- - \`<slug>\`는 배포된 팀의 실제 슬러그로 치환합니다.
684
- - "이 블록을 팀원에게 공유하면 바로 설치할 있습니다"라고 안내합니다.
816
+ - \`{owner}\`과 \`{slug}\`는 배포된 팀의 실제 슬러그에서 추출합니다 (\`@owner/slug\` → \`owner\`, \`slug\`).
817
+ - "이 블록을 팀원에게 공유하면 Claude가 환경 체크부터 설치까지 자동으로 해줍니다"라고 안내합니다.
818
+ - CLI가 이미 설치된 사용자를 위한 짧은 버전도 함께 표시: \`/relay:relay-install <slug>\`
685
819
  ${BUSINESS_CARD_FORMAT}
686
820
 
687
821
  ## 예시
@@ -697,10 +831,11 @@ ${BUSINESS_CARD_FORMAT}
697
831
  → AskUserQuestion: "이대로 배포할까요?" → ["배포", "취소"]
698
832
  → "배포" → relay publish 실행
699
833
  → "배포 완료! URL: https://relayax.com/@my-space/my-team"
700
- → 온보딩 가이드 코드블록 표시`,
834
+ → 온보딩 가이드 코드블록 표시
835
+ ${ERROR_HANDLING_GUIDE}`,
701
836
  },
702
837
  ];
703
838
  // ─── Builder Commands (로컬 설치) ───
704
839
  // relay-publish가 글로벌로 승격되어 현재 비어있음.
705
- // relay init --update만 실행하면 모든 커맨드가 한번에 업데이트됨.
840
+ // relay init --auto만 실행하면 모든 커맨드가 한번에 업데이트됨.
706
841
  exports.BUILDER_COMMANDS = [];
@@ -1,5 +1,13 @@
1
- /**
2
- * GUIDE.html 생성 프롬프트 템플릿.
3
- * publish slash command에서 Claude Code가 이 프롬프트를 읽고 GUIDE.html을 생성한다.
4
- */
5
- export declare const GUIDE_HTML_PROMPT: string;
1
+ import type { Requires } from '../commands/publish.js';
2
+ interface CommandEntry {
3
+ name: string;
4
+ description: string;
5
+ }
6
+ export declare function generateGuide(config: {
7
+ slug: string;
8
+ name: string;
9
+ description: string;
10
+ version: string;
11
+ visibility?: string;
12
+ }, commands: CommandEntry[], requires?: Requires): string;
13
+ export {};