relayax-cli 0.2.41 → 0.3.42

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.
Files changed (62) hide show
  1. package/dist/commands/access.js +12 -12
  2. package/dist/commands/changelog.js +2 -2
  3. package/dist/commands/check-update.js +12 -12
  4. package/dist/commands/create.js +46 -19
  5. package/dist/commands/deploy-record.js +2 -2
  6. package/dist/commands/diff.d.ts +2 -0
  7. package/dist/commands/diff.js +72 -0
  8. package/dist/commands/grant.d.ts +33 -0
  9. package/dist/commands/grant.js +190 -0
  10. package/dist/commands/init.js +10 -10
  11. package/dist/commands/install.js +125 -256
  12. package/dist/commands/join.d.ts +3 -2
  13. package/dist/commands/join.js +18 -69
  14. package/dist/commands/list.js +23 -26
  15. package/dist/commands/login.js +10 -3
  16. package/dist/commands/orgs.d.ts +10 -0
  17. package/dist/commands/orgs.js +128 -0
  18. package/dist/commands/outdated.js +7 -7
  19. package/dist/commands/package.d.ts +18 -0
  20. package/dist/commands/package.js +355 -146
  21. package/dist/commands/ping.js +5 -5
  22. package/dist/commands/publish.d.ts +1 -1
  23. package/dist/commands/publish.js +105 -103
  24. package/dist/commands/search.js +2 -2
  25. package/dist/commands/status.js +11 -11
  26. package/dist/commands/uninstall.js +7 -7
  27. package/dist/commands/update.js +22 -22
  28. package/dist/commands/versions.d.ts +2 -0
  29. package/dist/commands/versions.js +44 -0
  30. package/dist/index.js +8 -2
  31. package/dist/lib/ai-tools.d.ts +15 -0
  32. package/dist/lib/ai-tools.js +48 -1
  33. package/dist/lib/api.d.ts +13 -12
  34. package/dist/lib/api.js +24 -39
  35. package/dist/lib/command-adapter.js +41 -693
  36. package/dist/lib/config.d.ts +10 -5
  37. package/dist/lib/config.js +106 -24
  38. package/dist/lib/guide.js +34 -79
  39. package/dist/lib/installer.d.ts +2 -2
  40. package/dist/lib/installer.js +4 -4
  41. package/dist/lib/preamble.d.ts +4 -4
  42. package/dist/lib/preamble.js +14 -15
  43. package/dist/lib/slug.d.ts +5 -1
  44. package/dist/lib/slug.js +52 -9
  45. package/dist/lib/update-cache.js +4 -4
  46. package/dist/lib/version-check.d.ts +3 -3
  47. package/dist/lib/version-check.js +13 -13
  48. package/dist/prompts/_business-card.md +41 -0
  49. package/dist/prompts/_error-handling.md +38 -0
  50. package/dist/prompts/_requirements-check.md +59 -0
  51. package/dist/prompts/_setup-cli.md +19 -0
  52. package/dist/prompts/_setup-login.md +7 -0
  53. package/dist/prompts/_setup-org.md +27 -0
  54. package/dist/prompts/business-card.md +41 -0
  55. package/dist/prompts/error-handling.md +38 -0
  56. package/dist/prompts/index.d.ts +7 -0
  57. package/dist/prompts/index.js +28 -0
  58. package/dist/prompts/install.md +187 -0
  59. package/dist/prompts/publish.md +444 -0
  60. package/dist/prompts/requirements-check.md +59 -0
  61. package/dist/types.d.ts +10 -10
  62. package/package.json +3 -3
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.checkCliVersion = checkCliVersion;
4
- exports.checkTeamVersion = checkTeamVersion;
5
- exports.checkAllTeams = checkAllTeams;
4
+ exports.checkAgentVersion = checkAgentVersion;
5
+ exports.checkAllAgents = checkAllAgents;
6
6
  const config_js_1 = require("./config.js");
7
7
  const api_js_1 = require("./api.js");
8
8
  const update_cache_js_1 = require("./update-cache.js");
@@ -28,7 +28,7 @@ async function checkCliVersion(force) {
28
28
  }
29
29
  return null;
30
30
  }
31
- async function checkTeamVersion(slug, force) {
31
+ async function checkAgentVersion(slug, force) {
32
32
  if ((0, update_cache_js_1.isCacheValid)(slug, force))
33
33
  return null;
34
34
  try {
@@ -40,20 +40,20 @@ async function checkTeamVersion(slug, force) {
40
40
  if (entry.type === 'system') {
41
41
  return null;
42
42
  }
43
- const team = await (0, api_js_1.fetchTeamInfo)(slug);
43
+ const agent = await (0, api_js_1.fetchAgentInfo)(slug);
44
44
  (0, update_cache_js_1.updateCacheTimestamp)(slug);
45
45
  // Fire-and-forget usage ping (only when cache expired = actual API call happened)
46
- const teamId = entry.team_id ?? team.id;
47
- if (teamId) {
48
- (0, api_js_1.sendUsagePing)(teamId, slug, entry.version);
46
+ const agentId = entry.agent_id ?? agent.id;
47
+ if (agentId) {
48
+ (0, api_js_1.sendUsagePing)(agentId, slug, entry.version);
49
49
  }
50
- if (team.version !== entry.version) {
50
+ if (agent.version !== entry.version) {
51
51
  return {
52
- type: 'team',
52
+ type: 'agent',
53
53
  slug,
54
54
  current: entry.version,
55
- latest: team.version,
56
- author: team.author?.username,
55
+ latest: agent.version,
56
+ author: agent.author?.username,
57
57
  };
58
58
  }
59
59
  }
@@ -62,12 +62,12 @@ async function checkTeamVersion(slug, force) {
62
62
  }
63
63
  return null;
64
64
  }
65
- async function checkAllTeams(force) {
65
+ async function checkAllAgents(force) {
66
66
  const installed = (0, config_js_1.loadInstalled)();
67
67
  const slugs = Object.keys(installed);
68
68
  const results = [];
69
69
  for (const slug of slugs) {
70
- const result = await checkTeamVersion(slug, force);
70
+ const result = await checkAgentVersion(slug, force);
71
71
  if (result)
72
72
  results.push(result);
73
73
  }
@@ -0,0 +1,41 @@
1
+ ### 빌더 명함 표시
2
+ JSON 결과의 `author`, `welcome` 필드를 사용하여 명함을 표시합니다.
3
+ 불릿 리스트(- 또는 *)로 나열하지 마세요. 반드시 인용 블록(>) 안에 넣어야 합니다.
4
+
5
+ **JSON 결과에서 사용할 필드:**
6
+ - `author.display_name` 또는 `author.username` → 명함 제목
7
+ - `welcome` → 환영 메시지 (💬)
8
+ - `author.contact_links` → 연락처 배열 (`[{type, label, value}]`)
9
+ - `author.username` → 프로필 링크 (👤)
10
+
11
+ **예시 (이 형태를 그대로 따르세요):**
12
+
13
+ JSON 결과 예시:
14
+ ```json
15
+ {
16
+ "author": { "username": "alice", "display_name": "Alice Kim", "contact_links": [
17
+ {"type": "email", "label": "이메일", "value": "alice@example.com"},
18
+ {"type": "website", "label": "블로그", "value": "https://alice.dev"},
19
+ {"type": "kakao", "label": "카카오", "value": "https://open.kakao.com/o/abc123"}
20
+ ]},
21
+ "welcome": "안녕하세요!\n에이전트 빌더 Alice입니다.\n설치해주셔서 감사합니다."
22
+ }
23
+ ```
24
+
25
+ 출력:
26
+
27
+ > **🪪 Alice Kim의 명함**
28
+ >
29
+ > 💬 "안녕하세요!
30
+ > 에이전트 빌더 Alice입니다.
31
+ > 설치해주셔서 감사합니다."
32
+ >
33
+ > 📧 alice@example.com
34
+ > 🔗 블로그: alice.dev
35
+ > 💬 카카오: open.kakao.com/o/abc123
36
+ > 👤 relayax.com/@alice
37
+
38
+ - `welcome`이 없으면 💬 줄을 생략합니다.
39
+ - 연락처의 type에 맞는 이모지: 📧 email, 💬 kakao, 🐦 x, 💼 linkedin, 💻 github, 🔗 website/custom
40
+ - 연락처가 여러 개면 각각 한 줄씩 표시합니다.
41
+ - `author`가 null이면 명함 블록 전체를 생략합니다.
@@ -0,0 +1,38 @@
1
+ ### 에러 처리 가이드
2
+
3
+ CLI 명령 실행 후 JSON 에러가 반환되면 아래 기준에 따라 처리합니다.
4
+ **원칙: "되돌릴 수 없는 영향이 있는가?"로 판단합니다.**
5
+
6
+ #### 1. 자동 해결 (사용자에게 물어보지 않음)
7
+ 되돌릴 수 있고, 부작용 없는 에러:
8
+
9
+ | 에러 코드 | 행동 |
10
+ |-----------|------|
11
+ | `LOGIN_REQUIRED` / `NO_TOKEN` | `relay login` 실행 (timeout 300초, 브라우저 자동 열림) → 성공 후 원래 명령 재시도 |
12
+ | `NOT_INITIALIZED` | `relay init --all --json` 실행 → 원래 명령 재시도 |
13
+ | `FETCH_FAILED` | 3초 대기 후 원래 명령 재시도 (최대 2회). 2회 실패 시 사용자에게 안내 |
14
+
15
+ #### 2. 사용자에게 선택지 제시 (AskUserQuestion)
16
+ `options` 필드가 있는 에러:
17
+
18
+ | 에러 코드 | 행동 |
19
+ |-----------|------|
20
+ | `MISSING_VISIBILITY` | options의 label을 선택지로 AskUserQuestion 호출 |
21
+ | `MISSING_FIELD` | fix 안내 + 사용자에게 값 입력 요청 |
22
+ | `MISSING_TOOLS` | options의 감지된 도구 목록을 선택지로 AskUserQuestion 호출 |
23
+ | `MISSING_SPACE` | options의 Space 목록을 선택지로 AskUserQuestion 호출 |
24
+
25
+ 사용자가 선택하면, 선택된 값을 CLI 플래그에 반영하여 명령을 재호출합니다.
26
+
27
+ #### 3. 사용자에게 안내 (되돌릴 수 없는 에러)
28
+ 구매, 접근 권한, 보안 관련:
29
+
30
+ | 에러 코드 | 행동 |
31
+ |-----------|------|
32
+ | `GATED_ACCESS_REQUIRED` | purchase_info의 message/url 표시 → "접근 코드가 있으신가요?" AskUserQuestion |
33
+ | `SPACE_ONLY` | Space 가입 필요 안내 → "초대 코드가 있으신가요?" AskUserQuestion |
34
+ | `APPROVAL_REQUIRED` | 승인 대기 안내 |
35
+ | `NO_ACCESS` | 접근 방법 안내 |
36
+
37
+ #### 4. 그 외 에러
38
+ `fix` 필드의 메시지를 사용자에게 전달하고, 필요하면 다음 행동을 제안합니다.
@@ -0,0 +1,59 @@
1
+ ### Requirements 체크리스트 (필수 — 항목이 있으면 반드시 수행)
2
+
3
+ `relay.yaml`의 `requires` 섹션을 읽고, **각 항목을 하나씩 확인하여 체크리스트로 표시**합니다.
4
+ requires 섹션이 없거나 비어있으면 이 단계를 건너뜁니다.
5
+
6
+ **출력 형식** (반드시 이 형식으로 사용자에게 보여줍니다):
7
+ ```
8
+ 📋 Requirements 확인
9
+
10
+ [runtime]
11
+ ✅ Node.js >=18 — v20.11.0 확인됨
12
+ ❌ Python >=3.10 — v3.8.5 (업그레이드 필요)
13
+
14
+ [cli]
15
+ ✅ playwright — 설치됨
16
+ ❌ ffmpeg — 미설치 → 설치 명령: brew install ffmpeg
17
+
18
+ [npm]
19
+ ✅ sharp — 설치됨
20
+ ❌ puppeteer — 미설치 → 설치 중...
21
+
22
+ [env]
23
+ ✅ OPENAI_API_KEY — 설정됨
24
+ ❌ SLACK_WEBHOOK_URL (선택) — 미설정. 알림 전송에 필요
25
+
26
+ [mcp]
27
+ ⚙️ supabase — MCP 서버 설정 필요 (아래 안내 참고)
28
+
29
+ [agents]
30
+ ✅ @alice/doc-writer — 이미 설치됨
31
+ 📦 @bob/utils — 미설치 → 설치 중...
32
+ ```
33
+
34
+ **처리 규칙 (각 카테고리별):**
35
+
36
+ 1. **runtime**: `node --version`, `python3 --version`으로 확인. 버전 미달이면 ❌ 표시 후 업그레이드 안내.
37
+ 2. **cli**: `which <name>`으로 확인.
38
+ - 설치됨 → ✅
39
+ - 미설치 + `install` 필드 있음 → 사용자에게 설치할지 물어본 후 실행
40
+ - 미설치 + `install` 필드 없음 → ❌ 표시 후 수동 설치 안내
41
+ 3. **npm**: `npm list <package> 2>/dev/null`으로 확인.
42
+ - 설치됨 → ✅
43
+ - 미설치 + required → `npm install <package>` 실행
44
+ - 미설치 + optional → ❌ 표시 후 안내만
45
+ 4. **env**: `echo $<NAME>`으로 확인.
46
+ - 설정됨 → ✅
47
+ - 미설정 + required → ❌ 표시 후 `description`과 함께 설정 방법 안내
48
+ - 미설정 + optional → ⚠️ 표시 후 용도 안내
49
+ 5. **mcp**: MCP 서버 설정이 필요한 경우 ⚙️ 표시 후 설정 방법을 상세히 안내.
50
+ - `config` 필드가 있으면 settings.json에 추가할 JSON 블록을 보여줍니다.
51
+ - `env` 필드가 있으면 필요한 환경변수도 함께 안내합니다.
52
+ 6. **agents**: `relay list --json`으로 설치 여부 확인.
53
+ - 설치됨 → ✅
54
+ - 미설치 → `relay install <@author/agent> --json` 실행하여 재귀 설치
55
+
56
+ **중요**: 모든 required 항목이 ❌인 경우, 체크리스트 끝에 경고를 표시합니다:
57
+ ```
58
+ ⚠️ 필수 요구사항이 충족되지 않았습니다. 에이전트 기능이 제한될 수 있습니다.
59
+ ```
@@ -0,0 +1,19 @@
1
+ ## Step 1. Relay CLI 설치
2
+
3
+ relay CLI가 설치되어 있지 않다면:
4
+
5
+ ```bash
6
+ npm install -g relayax-cli
7
+ ```
8
+
9
+ Node.js가 없는 경우:
10
+ - macOS: `brew install node`
11
+ - Windows: `winget install OpenJS.NodeJS.LTS`
12
+ - Linux: `curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash && source ~/.bashrc && nvm install --lts`
13
+
14
+ npm 권한 오류 시:
15
+ ```bash
16
+ mkdir -p ~/.npm-global && npm config set prefix '~/.npm-global'
17
+ echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.zshrc && source ~/.zshrc
18
+ npm install -g relayax-cli
19
+ ```
@@ -0,0 +1,7 @@
1
+ ## Step 2. 로그인
2
+
3
+ ```bash
4
+ relay login
5
+ ```
6
+
7
+ 브라우저가 열리면 GitHub 또는 카카오 계정으로 로그인하세요.
@@ -0,0 +1,27 @@
1
+ ## Organization 생성
2
+
3
+ 먼저 Organization이 있는지 확인합니다:
4
+
5
+ ```bash
6
+ relay orgs list --json
7
+ ```
8
+
9
+ Organization이 없으면 새로 생성합니다:
10
+
11
+ ```bash
12
+ relay orgs create "조직 이름"
13
+ ```
14
+
15
+ 생성 후 접근 코드를 만들어 멤버를 초대할 수 있습니다:
16
+
17
+ ```bash
18
+ relay grant create --org <org-slug>
19
+ ```
20
+
21
+ 접근 코드를 받은 멤버는 아래 명령으로 Organization에 가입합니다:
22
+
23
+ ```bash
24
+ relay grant use --code <접근코드>
25
+ ```
26
+
27
+ 또는 웹에서 직접 관리할 수도 있습니다: relayax.com/orgs
@@ -0,0 +1,41 @@
1
+ ### 빌더 명함 표시
2
+ JSON 결과의 `author`, `welcome` 필드를 사용하여 명함을 표시합니다.
3
+ 불릿 리스트(- 또는 *)로 나열하지 마세요. 반드시 인용 블록(>) 안에 넣어야 합니다.
4
+
5
+ **JSON 결과에서 사용할 필드:**
6
+ - `author.display_name` 또는 `author.username` → 명함 제목
7
+ - `welcome` → 환영 메시지 (💬)
8
+ - `author.contact_links` → 연락처 배열 (`[{type, label, value}]`)
9
+ - `author.username` → 프로필 링크 (👤)
10
+
11
+ **예시 (이 형태를 그대로 따르세요):**
12
+
13
+ JSON 결과 예시:
14
+ ```json
15
+ {
16
+ "author": { "username": "alice", "display_name": "Alice Kim", "contact_links": [
17
+ {"type": "email", "label": "이메일", "value": "alice@example.com"},
18
+ {"type": "website", "label": "블로그", "value": "https://alice.dev"},
19
+ {"type": "kakao", "label": "카카오", "value": "https://open.kakao.com/o/abc123"}
20
+ ]},
21
+ "welcome": "안녕하세요!\n에이전트 빌더 Alice입니다.\n설치해주셔서 감사합니다."
22
+ }
23
+ ```
24
+
25
+ 출력:
26
+
27
+ > **🪪 Alice Kim의 명함**
28
+ >
29
+ > 💬 "안녕하세요!
30
+ > 에이전트 빌더 Alice입니다.
31
+ > 설치해주셔서 감사합니다."
32
+ >
33
+ > 📧 alice@example.com
34
+ > 🔗 블로그: alice.dev
35
+ > 💬 카카오: open.kakao.com/o/abc123
36
+ > 👤 relayax.com/@alice
37
+
38
+ - `welcome`이 없으면 💬 줄을 생략합니다.
39
+ - 연락처의 type에 맞는 이모지: 📧 email, 💬 kakao, 🐦 x, 💼 linkedin, 💻 github, 🔗 website/custom
40
+ - 연락처가 여러 개면 각각 한 줄씩 표시합니다.
41
+ - `author`가 null이면 명함 블록 전체를 생략합니다.
@@ -0,0 +1,38 @@
1
+ ### 에러 처리 가이드
2
+
3
+ CLI 명령 실행 후 JSON 에러가 반환되면 아래 기준에 따라 처리합니다.
4
+ **원칙: "되돌릴 수 없는 영향이 있는가?"로 판단합니다.**
5
+
6
+ #### 1. 자동 해결 (사용자에게 물어보지 않음)
7
+ 되돌릴 수 있고, 부작용 없는 에러:
8
+
9
+ | 에러 코드 | 행동 |
10
+ |-----------|------|
11
+ | `LOGIN_REQUIRED` / `NO_TOKEN` | `relay login` 실행 (timeout 300초, 브라우저 자동 열림) → 성공 후 원래 명령 재시도 |
12
+ | `NOT_INITIALIZED` | `relay init --all --json` 실행 → 원래 명령 재시도 |
13
+ | `FETCH_FAILED` | 3초 대기 후 원래 명령 재시도 (최대 2회). 2회 실패 시 사용자에게 안내 |
14
+
15
+ #### 2. 사용자에게 선택지 제시 (AskUserQuestion)
16
+ `options` 필드가 있는 에러:
17
+
18
+ | 에러 코드 | 행동 |
19
+ |-----------|------|
20
+ | `MISSING_VISIBILITY` | options의 label을 선택지로 AskUserQuestion 호출 |
21
+ | `MISSING_FIELD` | fix 안내 + 사용자에게 값 입력 요청 |
22
+ | `MISSING_TOOLS` | options의 감지된 도구 목록을 선택지로 AskUserQuestion 호출 |
23
+ | `MISSING_SPACE` | options의 Space 목록을 선택지로 AskUserQuestion 호출 |
24
+
25
+ 사용자가 선택하면, 선택된 값을 CLI 플래그에 반영하여 명령을 재호출합니다.
26
+
27
+ #### 3. 사용자에게 안내 (되돌릴 수 없는 에러)
28
+ 구매, 접근 권한, 보안 관련:
29
+
30
+ | 에러 코드 | 행동 |
31
+ |-----------|------|
32
+ | `GATED_ACCESS_REQUIRED` | purchase_info의 message/url 표시 → "접근 코드가 있으신가요?" AskUserQuestion |
33
+ | `SPACE_ONLY` | Space 가입 필요 안내 → "초대 코드가 있으신가요?" AskUserQuestion |
34
+ | `APPROVAL_REQUIRED` | 승인 대기 안내 |
35
+ | `NO_ACCESS` | 접근 방법 안내 |
36
+
37
+ #### 4. 그 외 에러
38
+ `fix` 필드의 메시지를 사용자에게 전달하고, 필요하면 다음 행동을 제안합니다.
@@ -0,0 +1,7 @@
1
+ export declare const REQUIREMENTS_CHECK: string;
2
+ export declare const ERROR_HANDLING_GUIDE: string;
3
+ export declare const BUSINESS_CARD_FORMAT: string;
4
+ export declare const SETUP_CLI: string;
5
+ export declare const SETUP_LOGIN: string;
6
+ export declare const INSTALL_PROMPT: string;
7
+ export declare const PUBLISH_PROMPT: string;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.PUBLISH_PROMPT = exports.INSTALL_PROMPT = exports.SETUP_LOGIN = exports.SETUP_CLI = exports.BUSINESS_CARD_FORMAT = exports.ERROR_HANDLING_GUIDE = exports.REQUIREMENTS_CHECK = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ function readPrompt(filename) {
10
+ return fs_1.default.readFileSync(path_1.default.join(__dirname, filename), 'utf-8').trim();
11
+ }
12
+ function interpolate(template, vars) {
13
+ return template.replace(/\{\{(\w+)\}\}/g, (_, key) => vars[key] ?? `{{${key}}}`);
14
+ }
15
+ // ─── 공유 조각 ───
16
+ exports.REQUIREMENTS_CHECK = readPrompt('_requirements-check.md');
17
+ exports.ERROR_HANDLING_GUIDE = readPrompt('_error-handling.md');
18
+ exports.BUSINESS_CARD_FORMAT = readPrompt('_business-card.md');
19
+ exports.SETUP_CLI = readPrompt('_setup-cli.md');
20
+ exports.SETUP_LOGIN = readPrompt('_setup-login.md');
21
+ const fragments = {
22
+ REQUIREMENTS_CHECK: exports.REQUIREMENTS_CHECK,
23
+ ERROR_HANDLING_GUIDE: exports.ERROR_HANDLING_GUIDE,
24
+ BUSINESS_CARD_FORMAT: exports.BUSINESS_CARD_FORMAT,
25
+ };
26
+ // ─── 전체 프롬프트 (조각 합성 완료) ───
27
+ exports.INSTALL_PROMPT = interpolate(readPrompt('install.md'), fragments);
28
+ exports.PUBLISH_PROMPT = interpolate(readPrompt('publish.md'), fragments);
@@ -0,0 +1,187 @@
1
+ 요청된 에이전트를 relay에서 다운로드하고, 현재 에이전트 환경에 맞게 구성합니다.
2
+ 인자 없이 호출하면 인터랙티브 탐색 모드로 진입합니다.
3
+
4
+ ## 인터랙션 플로우
5
+
6
+ 이 커맨드는 3단계 인터랙션으로 진행됩니다. 각 단계에서 반드시 AskUserQuestion 도구를 사용하세요.
7
+
8
+ ### Step 1. Organization 선택 & 에이전트 탐색 (slug가 없을 때만)
9
+
10
+ slug가 직접 주어지면 (`/relay-install @alice/doc-writer`) 이 단계를 건너뛰고 Step 2로 갑니다.
11
+
12
+ #### 1-1. Organization 선택
13
+ `relay orgs list --json` 을 실행하여 사용자의 Organization 목록을 가져옵니다.
14
+
15
+ **AskUserQuestion 호출:**
16
+ - question: "어디서 에이전트를 찾을까요?"
17
+ - options: Organization이 있으면 `["<org1_name>", "<org2_name>", ...]`, 없으면 이 단계를 건너뛰고 바로 공개 에이전트 탐색으로 진행
18
+
19
+ **응답 처리:**
20
+ - Organization 이름 선택 → 1-2. Org 에이전트 탐색으로 진행
21
+
22
+ #### 1-2. Org 에이전트 탐색
23
+ 선택된 Organization에서 에이전트를 검색합니다.
24
+ `relay search <keyword>` 명령어를 실행합니다 (필요하면 여러 키워드로 반복).
25
+ 또는 `relay list --org <org-slug> --json`으로 전체 목록을 가져옵니다.
26
+
27
+ 검색 결과를 번호 리스트로 보여줍니다:
28
+
29
+ ```
30
+ 검색 결과 (3개)
31
+
32
+ 1. @alice/doc-writer — 기술 문서 자동화
33
+ /write-doc, /api-doc
34
+
35
+ 2. @bob/code-reviewer — PR 리뷰 자동화
36
+ /review, /suggest
37
+
38
+ 3. @carol/test-gen — 테스트 코드 생성
39
+ /gen-test, /coverage
40
+ ```
41
+
42
+ **AskUserQuestion 호출:**
43
+ - question: "어떤 에이전트를 설치할까요?"
44
+ - options: `["1", "2", "3", "다시 검색", "돌아가기"]`
45
+
46
+ "다시 검색" → 새 키워드로 1-2 반복
47
+ "돌아가기" → 1-1로 돌아감
48
+ 번호 선택 → 해당 에이전트의 slug로 설치 진행
49
+
50
+ #### 1-3. Org 에이전트 목록 (전체 보기)
51
+ `relay list --org <org-slug> --json` 을 실행합니다.
52
+
53
+ 에이전트 목록을 번호 리스트로 보여줍니다 (1-2와 동일 형식).
54
+
55
+ **AskUserQuestion 호출:**
56
+ - question: "어떤 에이전트를 설치할까요?"
57
+ - options: `["1", "2", ..., "돌아가기"]`
58
+
59
+ "돌아가기" → 1-1로 돌아감
60
+ 번호 선택 → 해당 에이전트의 slug로 설치 진행
61
+
62
+ ### Step 2. 설치 & 배치 범위 선택
63
+
64
+ #### 2-1. 패키지 다운로드
65
+ `relay install <@org/agent> --json` 명령어를 실행합니다.
66
+ - 공개 에이전트 (public): `relay install <@org/agent> --json`
67
+ - 링크 공유 에이전트 (private): `relay install <slug> --json`
68
+ - 접근 권한이 없으면 CLI가 **purchase_info** (구매 안내 메시지 + URL)를 표시합니다.
69
+ - 접근 링크 코드가 있으면: `relay access <slug> --code <code>` 로 접근 부여 + 자동 설치를 한번에 수행합니다.
70
+ - Org 멤버이면 접근 확인 없이 바로 설치됩니다.
71
+ - Org 전용 에이전트 (internal): `relay install @<org-slug>/<agent-slug> --json`
72
+ - Org 가입이 필요하면: `relay join <org-slug> --code <invite-code>` 를 먼저 실행합니다.
73
+ - 또는 `--join-code <code>`로 가입+설치를 한번에 할 수 있습니다.
74
+ - CLI가 init과 login을 자동으로 처리합니다 (사용자가 별도 실행할 필요 없음).
75
+ - JSON 출력에서 `install_path` (패키지 경로)를 확인합니다.
76
+
77
+ **private 에이전트 접근 거부 처리:**
78
+ - CLI가 403 + `GATED_ACCESS_REQUIRED` 에러를 반환하면:
79
+ 1. purchase_info의 message와 url을 사용자에게 표시합니다.
80
+ 2. "접근 링크 코드가 있으신가요?"라고 물어봅니다.
81
+ 3. 코드가 있으면 `relay access <slug> --code <code>`를 실행합니다.
82
+ 4. 코드가 없으면 purchase_info의 url로 구매 안내합니다.
83
+
84
+ #### 2-2. 배치 범위 선택 (추천 포함)
85
+
86
+ 에이전트의 성격을 분석하여 글로벌/로컬 중 적합한 쪽을 추천합니다.
87
+
88
+ **추천 로직:**
89
+ - **글로벌 추천** — 범용 유틸리티 에이전트: 코드 리뷰, 문서 생성, 테스트, 번역 등 프로젝트에 무관하게 사용하는 도구성 에이전트
90
+ - **로컬 추천** — 프로젝트 특화 에이전트: 특정 프레임워크/스택 전용, 프로젝트별 워크플로우, 팀 내부 컨벤션에 의존하는 에이전트
91
+
92
+ 판단 기준 (다운로드된 패키지의 relay.yaml과 콘텐츠를 분석):
93
+ 1. `tags`에 특정 프레임워크/스택 키워드가 있으면 (nextjs, supabase, react 등) → 로컬 추천
94
+ 2. `requires`에 프로젝트 종속 의존성이 있으면 (npm 패키지, 특정 파일 구조) → 로컬 추천
95
+ 3. rules/ 파일이 있으면 (프로젝트 컨벤션 주입) → 로컬 추천
96
+ 4. 범용 키워드 (utility, review, docs, testing 등) → 글로벌 추천
97
+ 5. 판단이 어려우면 → 글로벌 추천 (기본값)
98
+
99
+ **AskUserQuestion 호출:**
100
+ - question: "{추천 이유}. {추천}에 설치할까요?"
101
+ - options: `["글로벌 (모든 프로젝트) ✓ 추천", "로컬 (이 프로젝트만)"]` 또는 `["글로벌 (모든 프로젝트)", "로컬 (이 프로젝트만) ✓ 추천"]`
102
+
103
+ 예시:
104
+ - "범용 코드 리뷰 도구입니다. 글로벌에 설치할까요?" → `["글로벌 (모든 프로젝트) ✓ 추천", "로컬 (이 프로젝트만)"]`
105
+ - "Next.js + Supabase 전용 에이전트입니다. 이 프로젝트에 설치할까요?" → `["글로벌 (모든 프로젝트)", "로컬 (이 프로젝트만) ✓ 추천"]`
106
+
107
+ **응답 처리:**
108
+ - "글로벌" → `~/.claude/`에 배치
109
+ - "로컬" → 현재 프로젝트 `.claude/`에 배치
110
+
111
+ #### 2-3. 에이전트 환경에 맞게 배치
112
+ 다운로드된 패키지(`install_path`)에서 파일을 읽고 선택된 범위에 배치합니다:
113
+ - 글로벌: `<install_path>/commands/` → `~/.claude/commands/`에 복사, skills/ 동일
114
+ - 로컬: `<install_path>/commands/` → `.claude/commands/`에 복사, skills/ 동일
115
+ - agents/, rules/ 파일도 같은 방식으로 배치합니다.
116
+ - **충돌 확인**: 같은 이름의 파일이 이미 있으면 사용자에게 덮어쓸지 물어봅니다.
117
+
118
+ #### 2-4. 배치 정보 기록 (필수)
119
+ 배치 완료 후 반드시 `relay deploy-record`를 실행합니다:
120
+ ```
121
+ relay deploy-record <slug> --scope <global|local> --files <배치된_파일1> <배치된_파일2> ...
122
+ ```
123
+
124
+ {{REQUIREMENTS_CHECK}}
125
+ {{ERROR_HANDLING_GUIDE}}
126
+
127
+ ### Step 3. 완료 & 팔로우 제안
128
+
129
+ #### 3-1. 완료 안내
130
+ - 배치된 파일과 활성화된 커맨드 목록을 보여줍니다.
131
+ {{BUSINESS_CARD_FORMAT}}
132
+
133
+ #### 3-2. 팔로우 제안 (필수 — 이 단계를 절대 건너뛰지 마세요)
134
+ 명함 표시 직후, 빌더의 username이 JSON 결과에 있으면 **반드시** AskUserQuestion 도구를 호출하세요.
135
+
136
+ **AskUserQuestion 호출:**
137
+ - question: `@{username}을 팔로우할까요? 새 버전 알림을 받을 수 있습니다.`
138
+ - options: `["팔로우", "건너뛰기"]`
139
+
140
+ **응답 처리:**
141
+ - "팔로우" → `relay follow @{username}` 실행. 로그인이 안 되어 있으면 `relay login` 먼저 실행 후 재시도.
142
+ - "건너뛰기" → 다음 단계로 진행
143
+
144
+ #### 3-3. 공유 가이드 (필수 — 설치 완료 시 반드시 표시)
145
+ 설치 완료 후 아래 공유용 설치 가이드를 표시합니다. 복사 가능한 코드 블록으로 보여줍니다.
146
+
147
+ ```
148
+ 주변인에게 공유하세요:
149
+
150
+ 아래 가이드를 읽고 그대로 따라해줘:
151
+ https://relayax.com/api/registry/{owner}/{slug}/guide.md
152
+ ```
153
+
154
+ - `{slug}`가 `@owner/agent` 형식이면 `{owner}`과 `{slug}`를 분리하여 URL을 구성합니다.
155
+ - CLI가 이미 설치된 사용자를 위한 짧은 버전도 함께 표시합니다: `/relay:relay-install {slug}`
156
+
157
+ #### 3-4. 사용 제안
158
+ - "바로 사용해볼까요?" 제안
159
+
160
+ #### 3-5. 업데이트 확인
161
+ - `relay check-update` 명령어를 실행합니다.
162
+ - CLI 업데이트가 있으면 안내합니다: "relay v{new} available. Run: npm update -g relayax-cli"
163
+ - 다른 에이전트 업데이트가 있으면 안내합니다.
164
+
165
+ ## 예시
166
+
167
+ ### 인터랙티브 모드 (/relay-install)
168
+ → relay orgs list --json 실행
169
+ → AskUserQuestion: "어디서 에이전트를 찾을까요?" → ["Alice's Org (alice)", "Acme Corp"]
170
+ → "Alice's Org" 선택 → "어떤 에이전트를 찾고 계세요?"
171
+ → relay search "문서" 실행 → 결과 리스트 표시
172
+ → AskUserQuestion: "어떤 에이전트를 설치할까요?" → ["1", "2", "3", "다시 검색"]
173
+ → "1" 선택 (@alice/doc-writer)
174
+ → AskUserQuestion: "어디에 설치할까요?" → ["글로벌 (모든 프로젝트)", "로컬 (이 프로젝트만)"]
175
+ → "글로벌" 선택
176
+ → 설치 + 배치 + deploy-record
177
+ → 명함 표시
178
+ → AskUserQuestion: "@alice을 팔로우할까요?" → ["팔로우", "건너뛰기"]
179
+ → "✓ 설치 완료! /write-doc를 사용해볼까요?"
180
+
181
+ ### 다이렉트 모드 (/relay-install @alice/doc-writer)
182
+ → relay install @alice/doc-writer --json 실행 (Step 1 건너뜀)
183
+ → AskUserQuestion: "어디에 설치할까요?" → ["글로벌 (모든 프로젝트)", "로컬 (이 프로젝트만)"]
184
+ → 설치 + 배치 + deploy-record
185
+ → 명함 표시
186
+ → AskUserQuestion: "@alice을 팔로우할까요?" → ["팔로우", "건너뛰기"]
187
+ → "✓ 설치 완료! /write-doc를 사용해볼까요?"