relayax-cli 0.1.97 → 0.1.98
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/commands/init.js +25 -1
- package/dist/commands/install.js +48 -27
- package/dist/lib/command-adapter.js +33 -27
- package/dist/lib/storage.js +1 -1
- package/package.json +1 -1
package/dist/commands/init.js
CHANGED
|
@@ -58,10 +58,23 @@ async function selectToolsInteractively(detectedIds) {
|
|
|
58
58
|
}
|
|
59
59
|
/**
|
|
60
60
|
* 글로벌 User 커맨드를 ~/.claude/commands/relay/에 설치한다.
|
|
61
|
+
* 기존 파일 중 현재 커맨드 목록에 없는 것은 제거한다.
|
|
61
62
|
*/
|
|
62
63
|
function installGlobalUserCommands() {
|
|
63
64
|
const globalDir = (0, command_adapter_js_1.getGlobalCommandDir)();
|
|
64
65
|
fs_1.default.mkdirSync(globalDir, { recursive: true });
|
|
66
|
+
// 현재 커맨드 ID 세트
|
|
67
|
+
const currentIds = new Set(command_adapter_js_1.USER_COMMANDS.map((c) => c.id));
|
|
68
|
+
// 기존 파일 중 현재 목록에 없는 것 제거
|
|
69
|
+
if (fs_1.default.existsSync(globalDir)) {
|
|
70
|
+
for (const file of fs_1.default.readdirSync(globalDir)) {
|
|
71
|
+
const id = file.replace(/\.md$/, '');
|
|
72
|
+
if (!currentIds.has(id)) {
|
|
73
|
+
fs_1.default.unlinkSync(path_1.default.join(globalDir, file));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// 현재 커맨드 설치 (덮어쓰기)
|
|
65
78
|
const commands = [];
|
|
66
79
|
for (const cmd of command_adapter_js_1.USER_COMMANDS) {
|
|
67
80
|
const filePath = (0, command_adapter_js_1.getGlobalCommandPath)(cmd.id);
|
|
@@ -160,12 +173,23 @@ function registerInit(program) {
|
|
|
160
173
|
targetToolIds = detected.map((t) => t.value);
|
|
161
174
|
}
|
|
162
175
|
}
|
|
163
|
-
// Builder 커맨드 설치
|
|
176
|
+
// Builder 커맨드 설치 (기존 파일 중 현재 목록에 없는 것 제거)
|
|
177
|
+
const builderIds = new Set(command_adapter_js_1.BUILDER_COMMANDS.map((c) => c.id));
|
|
164
178
|
for (const toolId of targetToolIds) {
|
|
165
179
|
const tool = ai_tools_js_1.AI_TOOLS.find((t) => t.value === toolId);
|
|
166
180
|
if (!tool)
|
|
167
181
|
continue;
|
|
168
182
|
const adapter = (0, command_adapter_js_1.createAdapter)(tool);
|
|
183
|
+
const localDir = path_1.default.join(projectPath, tool.skillsDir, 'commands', 'relay');
|
|
184
|
+
// 기존 로컬 커맨드 중 Builder 목록에 없는 것 제거
|
|
185
|
+
if (fs_1.default.existsSync(localDir)) {
|
|
186
|
+
for (const file of fs_1.default.readdirSync(localDir)) {
|
|
187
|
+
const id = file.replace(/\.md$/, '');
|
|
188
|
+
if (!builderIds.has(id)) {
|
|
189
|
+
fs_1.default.unlinkSync(path_1.default.join(localDir, file));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
169
193
|
const installedCommands = [];
|
|
170
194
|
for (const cmd of command_adapter_js_1.BUILDER_COMMANDS) {
|
|
171
195
|
const filePath = path_1.default.join(projectPath, adapter.getFilePath(cmd.id));
|
package/dist/commands/install.js
CHANGED
|
@@ -1,52 +1,72 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.registerInstall = registerInstall;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
4
9
|
const api_js_1 = require("../lib/api.js");
|
|
5
10
|
const storage_js_1 = require("../lib/storage.js");
|
|
6
|
-
const installer_js_1 = require("../lib/installer.js");
|
|
7
11
|
const config_js_1 = require("../lib/config.js");
|
|
8
12
|
function registerInstall(program) {
|
|
9
13
|
program
|
|
10
14
|
.command('install <slug>')
|
|
11
|
-
.description('에이전트 팀
|
|
12
|
-
.
|
|
13
|
-
.action(async (slug, opts) => {
|
|
15
|
+
.description('에이전트 팀 패키지를 .relay/teams/에 다운로드합니다')
|
|
16
|
+
.action(async (slug) => {
|
|
14
17
|
const json = program.opts().json ?? false;
|
|
15
|
-
const
|
|
18
|
+
const projectPath = process.cwd();
|
|
19
|
+
const teamDir = path_1.default.join(projectPath, '.relay', 'teams', slug);
|
|
16
20
|
const tempDir = (0, storage_js_1.makeTempDir)();
|
|
17
21
|
try {
|
|
18
22
|
// 1. Fetch team metadata
|
|
19
23
|
const team = await (0, api_js_1.fetchTeamInfo)(slug);
|
|
20
24
|
// 2. Visibility check
|
|
21
25
|
const visibility = team.visibility ?? 'public';
|
|
22
|
-
if (visibility === 'login-only') {
|
|
26
|
+
if (visibility === 'login-only' || visibility === 'invite-only') {
|
|
23
27
|
const token = (0, config_js_1.loadToken)();
|
|
24
28
|
if (!token) {
|
|
25
|
-
console.error(JSON.stringify({
|
|
29
|
+
console.error(JSON.stringify({
|
|
30
|
+
error: 'LOGIN_REQUIRED',
|
|
31
|
+
visibility,
|
|
32
|
+
slug,
|
|
33
|
+
message: visibility === 'invite-only'
|
|
34
|
+
? '이 팀은 초대받은 사용자만 설치할 수 있습니다. 로그인이 필요합니다.'
|
|
35
|
+
: '이 팀은 로그인이 필요합니다.',
|
|
36
|
+
}));
|
|
26
37
|
process.exit(1);
|
|
27
38
|
}
|
|
28
39
|
}
|
|
29
|
-
else if (visibility === 'invite-only') {
|
|
30
|
-
const token = (0, config_js_1.loadToken)();
|
|
31
|
-
if (!token) {
|
|
32
|
-
console.error(JSON.stringify({ error: 'LOGIN_REQUIRED', visibility: 'invite-only', slug, message: '이 팀은 초대받은 사용자만 설치할 수 있습니다. 로그인이 필요합니다.' }));
|
|
33
|
-
process.exit(1);
|
|
34
|
-
}
|
|
35
|
-
// 실제 초대 여부는 서버(install API)에서 체크
|
|
36
|
-
}
|
|
37
40
|
// 3. Download package
|
|
38
41
|
const tarPath = await (0, storage_js_1.downloadPackage)(team.package_url, tempDir);
|
|
39
|
-
// 4. Extract
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
// 4. Extract to .relay/teams/<slug>/
|
|
43
|
+
if (fs_1.default.existsSync(teamDir)) {
|
|
44
|
+
fs_1.default.rmSync(teamDir, { recursive: true, force: true });
|
|
45
|
+
}
|
|
46
|
+
fs_1.default.mkdirSync(teamDir, { recursive: true });
|
|
47
|
+
await (0, storage_js_1.extractPackage)(tarPath, teamDir);
|
|
48
|
+
// 5. Count extracted files
|
|
49
|
+
function countFiles(dir) {
|
|
50
|
+
let count = 0;
|
|
51
|
+
if (!fs_1.default.existsSync(dir))
|
|
52
|
+
return 0;
|
|
53
|
+
for (const entry of fs_1.default.readdirSync(dir, { withFileTypes: true })) {
|
|
54
|
+
if (entry.isDirectory()) {
|
|
55
|
+
count += countFiles(path_1.default.join(dir, entry.name));
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
count++;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return count;
|
|
62
|
+
}
|
|
63
|
+
const fileCount = countFiles(teamDir);
|
|
44
64
|
// 6. Record in installed.json
|
|
45
65
|
const installed = (0, config_js_1.loadInstalled)();
|
|
46
66
|
installed[slug] = {
|
|
47
67
|
version: team.version,
|
|
48
68
|
installed_at: new Date().toISOString(),
|
|
49
|
-
files,
|
|
69
|
+
files: [teamDir],
|
|
50
70
|
};
|
|
51
71
|
(0, config_js_1.saveInstalled)(installed);
|
|
52
72
|
// 7. Report install (non-blocking)
|
|
@@ -57,22 +77,23 @@ function registerInstall(program) {
|
|
|
57
77
|
slug,
|
|
58
78
|
version: team.version,
|
|
59
79
|
commands: team.commands,
|
|
60
|
-
|
|
61
|
-
install_path:
|
|
80
|
+
files: fileCount,
|
|
81
|
+
install_path: teamDir,
|
|
62
82
|
};
|
|
63
83
|
if (json) {
|
|
64
84
|
console.log(JSON.stringify(result));
|
|
65
85
|
}
|
|
66
86
|
else {
|
|
67
|
-
console.log(`\n\x1b[32m✓ ${team.name}
|
|
68
|
-
console.log(`
|
|
69
|
-
console.log(`
|
|
87
|
+
console.log(`\n\x1b[32m✓ ${team.name} 다운로드 완료\x1b[0m v${team.version}`);
|
|
88
|
+
console.log(` 위치: \x1b[36m${teamDir}\x1b[0m`);
|
|
89
|
+
console.log(` 파일: ${fileCount}개`);
|
|
70
90
|
if (team.commands.length > 0) {
|
|
71
|
-
console.log('\n
|
|
91
|
+
console.log('\n 포함된 커맨드:');
|
|
72
92
|
for (const cmd of team.commands) {
|
|
73
93
|
console.log(` \x1b[33m/${cmd.name}\x1b[0m - ${cmd.description}`);
|
|
74
94
|
}
|
|
75
95
|
}
|
|
96
|
+
console.log('\n 에이전트가 /relay-install로 환경을 구성합니다.');
|
|
76
97
|
}
|
|
77
98
|
}
|
|
78
99
|
catch (err) {
|
|
@@ -79,46 +79,52 @@ exports.USER_COMMANDS = [
|
|
|
79
79
|
{
|
|
80
80
|
id: 'relay-install',
|
|
81
81
|
description: 'relay 마켓플레이스에서 에이전트 팀을 설치합니다',
|
|
82
|
-
body: `요청된 에이전트 팀을 relay 마켓플레이스에서
|
|
82
|
+
body: `요청된 에이전트 팀을 relay 마켓플레이스에서 다운로드하고, 현재 에이전트 환경에 맞게 구성합니다.
|
|
83
83
|
|
|
84
84
|
## 실행 방법
|
|
85
85
|
|
|
86
|
-
### 1. 팀
|
|
86
|
+
### 1. 팀 패키지 다운로드
|
|
87
87
|
- \`relay install <slug>\` 명령어를 실행합니다.
|
|
88
|
-
-
|
|
89
|
-
|
|
90
|
-
### 2.
|
|
91
|
-
|
|
92
|
-
|
|
88
|
+
- 패키지가 \`.relay/teams/<slug>/\`에 다운로드됩니다.
|
|
89
|
+
|
|
90
|
+
### 2. 패키지 내용 확인
|
|
91
|
+
다운로드된 \`.relay/teams/<slug>/\` 디렉토리를 읽어 구조를 파악합니다:
|
|
92
|
+
- skills/ — 스킬 파일들
|
|
93
|
+
- commands/ — 슬래시 커맨드 파일들
|
|
94
|
+
- agents/ — 에이전트 설정 파일들
|
|
95
|
+
- rules/ — 룰 파일들
|
|
96
|
+
- relay.yaml — 팀 메타데이터 및 requirements
|
|
97
|
+
|
|
98
|
+
### 3. 에이전트 환경에 맞게 배치
|
|
99
|
+
현재 에이전트의 디렉토리 구조에 맞게 파일을 복사합니다:
|
|
100
|
+
- Claude Code: \`.relay/teams/<slug>/commands/\` → \`.claude/commands/\`에 복사
|
|
101
|
+
- Claude Code: \`.relay/teams/<slug>/skills/\` → \`.claude/skills/\`에 복사
|
|
102
|
+
- 다른 에이전트(Cursor, Cline 등): 해당 에이전트의 규칙에 맞는 디렉토리에 복사
|
|
103
|
+
- 에이전트 설정이나 룰은 적절한 위치에 배치
|
|
104
|
+
|
|
105
|
+
### 4. Requirements 확인 및 설치
|
|
106
|
+
\`.relay/teams/<slug>/relay.yaml\`의 \`requires\` 섹션을 읽고 처리합니다:
|
|
93
107
|
- **cli**: \`which <name>\`으로 확인 → 없으면 install 명령 실행 또는 안내
|
|
94
108
|
- **npm**: \`npm list <package>\`로 확인 → 없으면 \`npm install\`
|
|
95
|
-
- **env**: 환경변수 확인 →
|
|
96
|
-
- **mcp**: MCP 서버 설정 안내
|
|
97
|
-
- **
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
- API 응답의 \`welcome\` 필드가 있으면 제작자 메시지를 보여줍니다.
|
|
101
|
-
- \`contact\` 필드가 있으면 연락처도 함께 표시합니다.
|
|
102
|
-
|
|
103
|
-
### 4. 팔로우 제안
|
|
104
|
-
- "이 팀의 제작자 @username을 팔로우할까요?"라고 제안합니다.
|
|
105
|
-
- 인증되어 있으면 POST /api/follows로 팔로우합니다.
|
|
106
|
-
- 미인증이면 \`relay login\` 후 팔로우할 수 있다고 안내합니다.
|
|
109
|
+
- **env**: 환경변수 확인 → required이면 설정 안내, optional이면 알림
|
|
110
|
+
- **mcp**: MCP 서버 설정 — 에이전트의 MCP 설정에 추가 안내
|
|
111
|
+
- **runtime**: Node.js/Python 버전 확인
|
|
112
|
+
- **teams**: 의존하는 다른 팀 → \`relay install <team>\`으로 재귀 설치
|
|
113
|
+
${LOGIN_JIT_GUIDE}
|
|
107
114
|
|
|
108
115
|
### 5. 완료 안내
|
|
109
|
-
-
|
|
116
|
+
- 배치된 파일과 활성화된 커맨드 목록을 보여줍니다.
|
|
110
117
|
- "바로 사용해볼까요?" 제안
|
|
111
|
-
${LOGIN_JIT_GUIDE}
|
|
112
118
|
|
|
113
119
|
## 예시
|
|
114
120
|
|
|
115
121
|
사용자: /relay-install contents-team
|
|
116
|
-
→ relay install contents-team 실행
|
|
117
|
-
→
|
|
118
|
-
→
|
|
119
|
-
→
|
|
120
|
-
→
|
|
121
|
-
→ "
|
|
122
|
+
→ relay install contents-team 실행 (패키지 다운로드)
|
|
123
|
+
→ .relay/teams/contents-team/ 내용 확인
|
|
124
|
+
→ commands/cardnews.md → .claude/commands/cardnews.md 복사
|
|
125
|
+
→ skills/pdf-gen.md → .claude/skills/pdf-gen.md 복사
|
|
126
|
+
→ requires 확인: ✓ playwright 설치됨, ✓ sharp 설치됨
|
|
127
|
+
→ "✓ 설치 완료! /cardnews를 사용해볼까요?"`,
|
|
122
128
|
},
|
|
123
129
|
{
|
|
124
130
|
id: 'relay-list',
|
package/dist/lib/storage.js
CHANGED
|
@@ -33,7 +33,7 @@ async function extractPackage(tarPath, destDir) {
|
|
|
33
33
|
if (!fs_1.default.existsSync(destDir)) {
|
|
34
34
|
fs_1.default.mkdirSync(destDir, { recursive: true });
|
|
35
35
|
}
|
|
36
|
-
await (0, tar_1.extract)({ file: tarPath, cwd: destDir
|
|
36
|
+
await (0, tar_1.extract)({ file: tarPath, cwd: destDir });
|
|
37
37
|
}
|
|
38
38
|
function makeTempDir() {
|
|
39
39
|
return fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'relay-'));
|