relayax-cli 0.4.14 → 0.4.16

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.
@@ -8,7 +8,8 @@ import type { ContentType } from '../lib/ai-tools.js';
8
8
  export interface ContentEntry {
9
9
  name: string;
10
10
  type: ContentType;
11
- from: string;
11
+ from?: string;
12
+ path?: string;
12
13
  }
13
14
  type ContentDiffStatus = 'modified' | 'unchanged' | 'source_missing';
14
15
  interface ContentDiffEntry {
@@ -16,6 +16,14 @@ const js_yaml_1 = __importDefault(require("js-yaml"));
16
16
  const ai_tools_js_1 = require("../lib/ai-tools.js");
17
17
  const paths_js_1 = require("../lib/paths.js");
18
18
  const SYNC_DIRS = ['skills', 'commands', 'agents', 'rules'];
19
+ /** from 또는 path 중 존재하는 값을 반환 */
20
+ function getFromPath(entry) {
21
+ const val = entry.from ?? entry.path;
22
+ if (!val) {
23
+ throw new Error(`contents 항목 "${entry.name}"에 from 또는 path가 필요합니다.`);
24
+ }
25
+ return val;
26
+ }
19
27
  // ─── Helpers ───
20
28
  function fileHash(filePath) {
21
29
  const content = fs_1.default.readFileSync(filePath);
@@ -121,7 +129,7 @@ function scanPath(absPath) {
121
129
  function computeContentsDiff(contents, relayDir, projectPath) {
122
130
  const diff = [];
123
131
  for (const entry of contents) {
124
- const absFrom = resolveFromPath(entry.from, projectPath);
132
+ const absFrom = resolveFromPath(getFromPath(entry), projectPath);
125
133
  if (!fs_1.default.existsSync(absFrom)) {
126
134
  diff.push({ name: entry.name, type: entry.type, status: 'source_missing' });
127
135
  continue;
@@ -152,7 +160,8 @@ function computeContentsDiff(contents, relayDir, projectPath) {
152
160
  * ~/.claude/agents/dev-lead.md → agents/dev-lead.md
153
161
  */
154
162
  function deriveRelaySubPath(entry) {
155
- const from = entry.from.startsWith('~/') ? entry.from.slice(2) : entry.from;
163
+ const fromPath = getFromPath(entry);
164
+ const from = fromPath.startsWith('~/') ? fromPath.slice(2) : fromPath;
156
165
  // skills/xxx, agents/xxx 등의 패턴을 추출
157
166
  for (const dir of SYNC_DIRS) {
158
167
  const idx = from.indexOf(`/${dir}/`);
@@ -211,7 +220,7 @@ function syncContentsToRelay(contents, contentsDiff, relayDir, projectPath) {
211
220
  const content = contents.find((c) => c.name === diffEntry.name && c.type === diffEntry.type);
212
221
  if (!content)
213
222
  continue;
214
- const absFrom = resolveFromPath(content.from, projectPath);
223
+ const absFrom = resolveFromPath(getFromPath(content), projectPath);
215
224
  const relaySubPath = deriveRelaySubPath(content);
216
225
  const relayTarget = path_1.default.join(relayDir, relaySubPath);
217
226
  // 단일 파일인 경우 직접 복사 (디렉토리 기반 diff/sync 불필요)
@@ -784,28 +784,28 @@ function registerPublish(program) {
784
784
  // preamble update is best-effort — publish already succeeded
785
785
  }
786
786
  if (json) {
787
- console.log(JSON.stringify(result));
787
+ // Enrich JSON output with plugin_url if git_url available
788
+ const jsonResult = { ...result };
789
+ const resultGitUrl = jsonResult.git_url;
790
+ if (resultGitUrl) {
791
+ const pSlug = jsonResult.slug.startsWith('@') ? jsonResult.slug.slice(1) : jsonResult.slug;
792
+ const pName = pSlug.includes('/') ? pSlug.split('/')[1] : pSlug;
793
+ jsonResult.plugin_url = `${config_js_1.API_URL}/api/registry/${pSlug}/plugin`;
794
+ jsonResult.plugin_install_cmd = `/plugin install ${pName}`;
795
+ }
796
+ jsonResult.platforms = generatedPlatforms;
797
+ console.log(JSON.stringify(jsonResult));
788
798
  }
789
799
  else {
790
800
  console.log(`\n\x1b[32m✓ ${config.name} 배포 완료\x1b[0m v${result.version}`);
791
801
  console.log(` 슬러그: \x1b[36m${result.slug}\x1b[0m`);
792
802
  console.log(` URL: \x1b[36m${result.url}\x1b[0m`);
793
- // Show generated platform manifests
794
- if (generatedPlatforms.length > 0) {
795
- console.log(`\n \x1b[90m플랫폼 매니페스트:\x1b[0m ${generatedPlatforms.join(', ')}`);
796
- }
797
- // Show Claude Code plugin install command if claude-code manifest was generated
798
- if (generatedPlatforms.includes('claude-code')) {
799
- const pluginSlug = result.slug.startsWith('@') ? result.slug.slice(1) : result.slug;
800
- const pluginUrl = `${config_js_1.API_URL}/api/registry/@${pluginSlug}/plugin`;
801
- console.log(`\n \x1b[90mClaude Code 플러그인:\x1b[0m`);
802
- console.log(` \x1b[36m/plugin marketplace add ${pluginUrl}\x1b[0m`);
803
- }
804
- // Show shareable onboarding guide as a plain copyable block
805
- if (isTTY) {
803
+ // Build share block
804
+ {
806
805
  const detailSlug = result.slug.startsWith('@') ? result.slug.slice(1) : result.slug;
807
806
  const accessCode = result.access_code;
808
- // Primary: npx 설치 명령어 한 줄
807
+ const gitUrl = result.git_url;
808
+ // CLI install command
809
809
  const visibility = config.visibility ?? 'public';
810
810
  let installCmd;
811
811
  if (visibility === 'internal' && accessCode) {
@@ -817,12 +817,46 @@ function registerPublish(program) {
817
817
  else {
818
818
  installCmd = `npx relayax-cli install ${result.slug}`;
819
819
  }
820
- console.log(`\n \x1b[90m공유하세요:\x1b[0m`);
821
- console.log(` ┌${''.repeat(installCmd.length + 2)}┐`);
822
- console.log(` │ ${installCmd} │`);
823
- console.log(` └${'─'.repeat(installCmd.length + 2)}┘`);
824
- // Secondary: 에이전트 소개 페이지
825
- console.log(`\n \x1b[90m에이전트 소개: \x1b[36mhttps://relayax.com/@${detailSlug}\x1b[0m`);
820
+ // Plugin install commands (marketplace add + plugin install)
821
+ const pluginSlug = detailSlug.includes('/') ? detailSlug.split('/')[1] : detailSlug;
822
+ const pluginUrl = gitUrl ? `${config_js_1.API_URL}/api/registry/${detailSlug}/plugin` : null;
823
+ // ── CLI 설치 (복사용) ──
824
+ console.log(`\n \x1b[1m▸ CLI 설치\x1b[0m`);
825
+ console.log(` ┌─`);
826
+ console.log(` │ ${installCmd}`);
827
+ console.log(` └─`);
828
+ // ── Claude Code Plugin 설치 (복사용) ──
829
+ if (pluginUrl) {
830
+ console.log(`\n \x1b[1m▸ Claude Code Plugin 설치\x1b[0m`);
831
+ console.log(` ┌─`);
832
+ console.log(` │ /plugin marketplace add ${pluginUrl}`);
833
+ console.log(` │ /plugin install ${pluginSlug}`);
834
+ console.log(` └─`);
835
+ }
836
+ // ── 소개 페이지 ──
837
+ console.log(`\n \x1b[90m소개 페이지:\x1b[0m https://relayax.com/@${detailSlug}`);
838
+ // ── 공유 텍스트 (코드블록, 그대로 복붙) ──
839
+ if (isTTY) {
840
+ const shareBlock = [
841
+ `[${config.name}] 설치하기`,
842
+ ``,
843
+ `# CLI`,
844
+ installCmd,
845
+ ];
846
+ if (pluginUrl) {
847
+ shareBlock.push(``, `# Claude Code Plugin`, `/plugin marketplace add ${pluginUrl}`, `/plugin install ${pluginSlug}`);
848
+ }
849
+ shareBlock.push(``, `소개: https://relayax.com/@${detailSlug}`);
850
+ const maxLen = Math.max(...shareBlock.map((l) => l.length));
851
+ const border = '─'.repeat(maxLen + 2);
852
+ console.log(`\n \x1b[90m┌${border}┐\x1b[0m`);
853
+ for (const line of shareBlock) {
854
+ const pad = ' '.repeat(maxLen - line.length);
855
+ console.log(` \x1b[90m│\x1b[0m ${line}${pad} \x1b[90m│\x1b[0m`);
856
+ }
857
+ console.log(` \x1b[90m└${border}┘\x1b[0m`);
858
+ console.log(` \x1b[90m↑ 팀에 공유하세요\x1b[0m`);
859
+ }
826
860
  }
827
861
  }
828
862
  }
@@ -3,6 +3,27 @@ relay.yaml이 없으면 새로 만들고, 있으면 변경사항을 반영합니
3
3
 
4
4
  > 빌더는 터미널 환경에서 작업합니다. CLI 명령어를 직접 실행하세요.
5
5
 
6
+ ## 핵심 원칙: 의사결정 포인트에서 사용자 질문
7
+
8
+ 각 단계에서 **선택지가 2개 이상**이면 반드시 사용자에게 질문하고 답변을 기다리세요.
9
+ (AskUserQuestion 등 사용자 입력을 받는 도구를 사용하세요.)
10
+ 선택지가 1개뿐이거나 자동 판단이 가능하면 결과를 보여주고 바로 진행합니다.
11
+
12
+ ### 사용자 입력이 필요한 경우 (멈추고 질문하고 답변을 기다림)
13
+ - 소스가 2개 이상 감지됨 → "어떤 콘텐츠를 포함할까요?"
14
+ - Org가 1개 이상 있음 → "개인 배포 vs Org 배포?"
15
+ - visibility 옵션이 2개 이상 → "공개 범위를 선택해주세요"
16
+ - 포지셔닝 확인 → 분석 결과를 보여주고 "이대로 진행할까요?"
17
+ - 배포 최종 확인 → relay.yaml 요약을 보여주고 "배포할까요?"
18
+
19
+ **질문 후 반드시 사용자의 답변을 받을 때까지 다음 단계로 넘어가지 마세요.**
20
+ 텍스트로 질문을 출력한 뒤 혼자 답변하고 진행하면 안 됩니다.
21
+
22
+ ### 자동 진행하는 경우 (질문 불필요)
23
+ - 소스가 1개뿐 → 해당 소스 자동 선택, 결과만 보여줌
24
+ - 이미 relay.yaml이 있고 변경사항이 명확함 → 요약 후 진행
25
+ - 로그인이 필요 → 자동으로 `relay login` 실행
26
+
6
27
  ## 분기: 최초 생성 vs 업데이트
7
28
 
8
29
  `.relay/relay.yaml`이 있는지 확인합니다.
@@ -17,7 +38,9 @@ relay.yaml이 없으면 새로 만들고, 있으면 변경사항을 반영합니
17
38
  ### 1. 콘텐츠 파악
18
39
 
19
40
  `relay package --init --json`으로 소스를 스캔합니다.
20
- 결과의 `sources[]`에서 사용자에게 어떤 콘텐츠를 포함할지 물어봅니다.
41
+
42
+ - **소스가 2개 이상** → `sources[]`를 정리하여 보여주고, **사용자에게 질문하여 어떤 콘텐츠를 포함할지 물어봅니다.** 사용자가 답변할 때까지 다음 단계로 넘어가지 마세요.
43
+ - **소스가 1개** → 해당 소스를 자동 선택하고 결과를 보여준 뒤 바로 진행합니다.
21
44
 
22
45
  선택된 콘텐츠의 파일을 직접 읽어 기능을 파악합니다:
23
46
  - SKILL.md, 에이전트 파일, 커맨드 파일의 내용
@@ -35,6 +58,9 @@ relay.yaml이 없으면 새로 만들고, 있으면 변경사항을 반영합니
35
58
  이름(name)은 한국어 가능. slug는 영문 소문자+하이픈.
36
59
  설명은 설치자 관점으로 ("~를 자동화합니다").
37
60
 
61
+ 포지셔닝 결과를 표로 정리하여 보여주고, **사용자에게 질문하여 확인받으세요.**
62
+ ("이 포지셔닝으로 진행할까요? 수정할 부분이 있으면 알려주세요.")
63
+
38
64
  ### 3. requires 판단 + 보안 점검
39
65
 
40
66
  콘텐츠 파일을 읽고 requires를 판단합니다:
@@ -53,22 +79,30 @@ relay.yaml이 없으면 새로 만들고, 있으면 변경사항을 반영합니
53
79
  - 파일 컨텍스트를 읽어 실제 시크릿 vs 예시 코드를 구분
54
80
  - 발견 시 **반드시 경고**하고 환경변수 대체 안내
55
81
 
56
- ### 4. relay.yaml 작성 & 배포
82
+ ### 4. 배포 설정
57
83
 
58
- 판단 결과를 relay.yaml에 반영합니다:
84
+ `relay orgs list --json`으로 Org 목록을 조회합니다.
85
+
86
+ **항상 사용자에게 질문합니다** (AskUserQuestion 등 사용자 입력 도구 사용):
87
+ - **Org가 1개 이상** → "개인 배포 / {org이름}에 배포" 선택
88
+ - **Org가 없음** → "개인 배포 / 새 Organization 만들기" 선택
89
+ - "새 Organization 만들기" 선택 시 → `relay orgs create "이름"` 실행 후 해당 Org에 배포
90
+
91
+ 선택에 따라 **사용자에게 질문하여 visibility를 물어봅니다:**
92
+ - **Org 없이 배포**: `public`, `private` (2개)
93
+ - **Org에 배포**: `public`, `private`, `internal` (3개)
94
+ - `public` — 누구나 설치
95
+ - `private` — 접근 링크가 있는 사람만 설치
96
+ - `internal` — Org 멤버만 설치 (Org 배포 시에만 선택 가능)
97
+
98
+ ### 5. relay.yaml 작성 & 배포
99
+
100
+ 위 결과를 relay.yaml에 반영합니다:
59
101
  - name, slug, description, version, tags
60
102
  - requires (판단 결과)
61
- - org: `relay orgs list --json`으로 Org 목록을 조회합니다.
62
- - Org가 있으면: 개인 배포 vs Org 배포를 사용자에게 물어봅니다.
63
- - Org가 없으면: 개인 배포로 진행합니다.
64
- - visibility: Org 선택 결과에 따라 옵션이 달라집니다:
65
- - **Org 없이 배포**: `public`, `private` (2개)
66
- - **Org에 배포**: `public`, `private`, `internal` (3개)
67
- - `public` — 누구나 설치
68
- - `private` — 접근 링크가 있는 사람만 설치
69
- - `internal` — Org 멤버만 설치 (Org 배포 시에만 선택 가능)
103
+ - org, visibility
70
104
 
71
- `relay publish --json`으로 배포합니다.
105
+ **사용자에게 질문하여 최종 확인** 후 `relay publish --json`으로 배포합니다.
72
106
 
73
107
  ---
74
108
 
@@ -80,7 +114,7 @@ relay.yaml이 없으면 새로 만들고, 있으면 변경사항을 반영합니
80
114
  - 변경된 콘텐츠 (modified)
81
115
  - 새로 추가된 콘텐츠 (new_items)
82
116
 
83
- 사용자에게 어떤 부분을 변경하려는지 물어봅니다:
117
+ **사용자에게 질문하여 어떤 부분을 변경하려는지 물어봅니다:**
84
118
  - 콘텐츠 변경 반영 (sync)
85
119
  - 새 스킬/커맨드 추가
86
120
  - 설명/태그 개선
@@ -96,13 +130,25 @@ relay.yaml이 없으면 새로 만들고, 있으면 변경사항을 반영합니
96
130
 
97
131
  ### 3. 배포
98
132
 
133
+ 변경 요약을 보여주고 **사용자에게 질문하여 최종 확인** 후 배포합니다.
99
134
  `relay publish --json`으로 배포합니다.
100
- 버전 범프가 필요하면 사용자에게 patch/minor/major 중 확인합니다.
135
+ 버전 범프가 필요하면 사용자에게 질문하여 patch/minor/major 중 확인합니다.
101
136
 
102
137
  ---
103
138
 
104
- ## 공유 문구
139
+ ## 배포 완료 후 공유 안내
140
+
141
+ `relay publish --json` 출력 결과를 파싱하여 다음을 보여주세요:
105
142
 
106
- 배포 완료 `relay publish` 출력에 포함된 공유 문구를 보여줍니다.
143
+ 1. **배포 결과 요약** slug, 버전, 공개 범위, URL
144
+ 2. **설치 방법** — CLI 출력에 코드블록 형태로 이미 포함되어 있으므로, 그 내용을 사용자에게 안내합니다:
145
+ - CLI: `npx relayax-cli install {slug}`
146
+ - 출력에 `plugin_url`이 있으면 Claude Code Plugin:
147
+ ```
148
+ /plugin marketplace add {pluginUrl}
149
+ /plugin install {slug}
150
+ ```
151
+ - 에이전트 소개 페이지 URL
152
+ 3. **공유 텍스트** — CLI 출력의 공유 블록(┌─ ... ─┘)을 그대로 안내합니다. 팀에 바로 복붙할 수 있는 코드블록 형태입니다.
107
153
 
108
154
  {{ERROR_HANDLING_GUIDE}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relayax-cli",
3
- "version": "0.4.14",
3
+ "version": "0.4.16",
4
4
  "description": "RelayAX Agent Team Marketplace CLI - Install and manage agent teams",
5
5
  "main": "dist/index.js",
6
6
  "bin": {