relayax-cli 0.1.93 → 0.1.94
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/create.js +9 -7
- package/dist/commands/init.js +113 -2
- package/dist/commands/publish.js +26 -10
- package/dist/commands/status.js +1 -1
- package/dist/lib/command-adapter.js +8 -8
- package/dist/lib/config.js.bak +75 -0
- package/package.json +2 -1
package/dist/commands/create.js
CHANGED
|
@@ -31,15 +31,16 @@ function registerCreate(program) {
|
|
|
31
31
|
.action(async (name) => {
|
|
32
32
|
const json = program.opts().json ?? false;
|
|
33
33
|
const projectPath = process.cwd();
|
|
34
|
-
const
|
|
34
|
+
const relayDir = path_1.default.join(projectPath, '.relay');
|
|
35
|
+
const relayYamlPath = path_1.default.join(relayDir, 'relay.yaml');
|
|
35
36
|
const isTTY = Boolean(process.stdin.isTTY) && !json;
|
|
36
|
-
// 1. relay.yaml 이미 존재하면 에러
|
|
37
|
+
// 1. .relay/relay.yaml 이미 존재하면 에러
|
|
37
38
|
if (fs_1.default.existsSync(relayYamlPath)) {
|
|
38
39
|
if (json) {
|
|
39
|
-
console.error(JSON.stringify({ error: 'ALREADY_EXISTS', message: 'relay.yaml이 이미 존재합니다.' }));
|
|
40
|
+
console.error(JSON.stringify({ error: 'ALREADY_EXISTS', message: '.relay/relay.yaml이 이미 존재합니다.' }));
|
|
40
41
|
}
|
|
41
42
|
else {
|
|
42
|
-
console.error('relay.yaml이 이미 존재합니다. 기존 팀 프로젝트에서는 `relay init`을 사용하세요.');
|
|
43
|
+
console.error('.relay/relay.yaml이 이미 존재합니다. 기존 팀 프로젝트에서는 `relay init`을 사용하세요.');
|
|
43
44
|
}
|
|
44
45
|
process.exit(1);
|
|
45
46
|
}
|
|
@@ -69,7 +70,8 @@ function registerCreate(program) {
|
|
|
69
70
|
],
|
|
70
71
|
});
|
|
71
72
|
}
|
|
72
|
-
// 3. relay.yaml 생성
|
|
73
|
+
// 3. .relay/relay.yaml 생성
|
|
74
|
+
fs_1.default.mkdirSync(relayDir, { recursive: true });
|
|
73
75
|
const yamlData = {
|
|
74
76
|
name,
|
|
75
77
|
slug: defaultSlug,
|
|
@@ -80,7 +82,7 @@ function registerCreate(program) {
|
|
|
80
82
|
};
|
|
81
83
|
fs_1.default.writeFileSync(relayYamlPath, js_yaml_1.default.dump(yamlData, { lineWidth: 120 }), 'utf-8');
|
|
82
84
|
// 4. 디렉토리 구조 생성
|
|
83
|
-
const createdDirs = [];
|
|
85
|
+
const createdDirs = ['.relay'];
|
|
84
86
|
for (const dir of DEFAULT_DIRS) {
|
|
85
87
|
const dirPath = path_1.default.join(projectPath, dir);
|
|
86
88
|
if (!fs_1.default.existsSync(dirPath)) {
|
|
@@ -118,7 +120,7 @@ function registerCreate(program) {
|
|
|
118
120
|
}
|
|
119
121
|
else {
|
|
120
122
|
console.log(`\n\x1b[32m✓ ${name} 팀 프로젝트 생성 완료\x1b[0m\n`);
|
|
121
|
-
console.log(` relay.yaml 생성됨`);
|
|
123
|
+
console.log(` .relay/relay.yaml 생성됨`);
|
|
122
124
|
if (createdDirs.length > 0) {
|
|
123
125
|
console.log(` 디렉토리 생성: ${createdDirs.join(', ')}`);
|
|
124
126
|
}
|
package/dist/commands/init.js
CHANGED
|
@@ -77,10 +77,10 @@ function hasGlobalUserCommands() {
|
|
|
77
77
|
return command_adapter_js_1.USER_COMMANDS.every((cmd) => fs_1.default.existsSync((0, command_adapter_js_1.getGlobalCommandPath)(cmd.id)));
|
|
78
78
|
}
|
|
79
79
|
/**
|
|
80
|
-
* 팀 프로젝트인지 감지한다 (relay.yaml 또는 팀 디렉토리 구조).
|
|
80
|
+
* 팀 프로젝트인지 감지한다 (.relay/relay.yaml 또는 팀 디렉토리 구조).
|
|
81
81
|
*/
|
|
82
82
|
function isTeamProject(projectPath) {
|
|
83
|
-
if (fs_1.default.existsSync(path_1.default.join(projectPath, 'relay.yaml'))) {
|
|
83
|
+
if (fs_1.default.existsSync(path_1.default.join(projectPath, '.relay', 'relay.yaml'))) {
|
|
84
84
|
return true;
|
|
85
85
|
}
|
|
86
86
|
return VALID_TEAM_DIRS.some((d) => {
|
|
@@ -90,6 +90,80 @@ function isTeamProject(projectPath) {
|
|
|
90
90
|
return fs_1.default.readdirSync(dirPath).filter((f) => !f.startsWith('.')).length > 0;
|
|
91
91
|
});
|
|
92
92
|
}
|
|
93
|
+
/** 레거시 User 커맨드 ID (이전에 로컬에 설치되던 것) */
|
|
94
|
+
const LEGACY_LOCAL_COMMAND_IDS = ['relay-explore', 'relay-install', 'relay-publish'];
|
|
95
|
+
/**
|
|
96
|
+
* 레거시 구조를 감지하고 마이그레이션한다.
|
|
97
|
+
* - relay.yaml (루트) → .relay/relay.yaml
|
|
98
|
+
* - portfolio/ (루트) → .relay/portfolio/
|
|
99
|
+
* - 로컬 레거시 슬래시 커맨드 제거 (글로벌로 이동되므로)
|
|
100
|
+
*/
|
|
101
|
+
function detectLegacy(projectPath) {
|
|
102
|
+
const details = [];
|
|
103
|
+
if (fs_1.default.existsSync(path_1.default.join(projectPath, 'relay.yaml'))) {
|
|
104
|
+
details.push('relay.yaml → .relay/relay.yaml');
|
|
105
|
+
}
|
|
106
|
+
const legacyPortfolio = path_1.default.join(projectPath, 'portfolio');
|
|
107
|
+
if (fs_1.default.existsSync(legacyPortfolio) && fs_1.default.statSync(legacyPortfolio).isDirectory()) {
|
|
108
|
+
const hasImages = fs_1.default.readdirSync(legacyPortfolio).some((f) => ['.png', '.jpg', '.jpeg', '.webp'].some((ext) => f.toLowerCase().endsWith(ext)));
|
|
109
|
+
if (hasImages) {
|
|
110
|
+
details.push('portfolio/ → .relay/portfolio/');
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// 로컬에 레거시 슬래시 커맨드가 있는지 확인
|
|
114
|
+
const detected = (0, ai_tools_js_1.detectAgentCLIs)(projectPath);
|
|
115
|
+
for (const tool of detected) {
|
|
116
|
+
const cmdDir = path_1.default.join(projectPath, tool.skillsDir, 'commands', 'relay');
|
|
117
|
+
if (!fs_1.default.existsSync(cmdDir))
|
|
118
|
+
continue;
|
|
119
|
+
for (const cmdId of LEGACY_LOCAL_COMMAND_IDS) {
|
|
120
|
+
if (fs_1.default.existsSync(path_1.default.join(cmdDir, `${cmdId}.md`))) {
|
|
121
|
+
details.push(`${tool.skillsDir}/commands/relay/${cmdId}.md 제거 (글로벌로 이동)`);
|
|
122
|
+
break; // 한 번만 표시
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return { hasLegacy: details.length > 0, details };
|
|
127
|
+
}
|
|
128
|
+
function runMigration(projectPath) {
|
|
129
|
+
const result = { relayYaml: false, portfolio: false, localCommandsCleaned: 0 };
|
|
130
|
+
const relayDir = path_1.default.join(projectPath, '.relay');
|
|
131
|
+
// 1. relay.yaml 이동
|
|
132
|
+
const legacyYaml = path_1.default.join(projectPath, 'relay.yaml');
|
|
133
|
+
const newYaml = path_1.default.join(relayDir, 'relay.yaml');
|
|
134
|
+
if (fs_1.default.existsSync(legacyYaml) && !fs_1.default.existsSync(newYaml)) {
|
|
135
|
+
fs_1.default.mkdirSync(relayDir, { recursive: true });
|
|
136
|
+
fs_1.default.renameSync(legacyYaml, newYaml);
|
|
137
|
+
result.relayYaml = true;
|
|
138
|
+
}
|
|
139
|
+
// 2. portfolio/ 이동
|
|
140
|
+
const legacyPortfolio = path_1.default.join(projectPath, 'portfolio');
|
|
141
|
+
const newPortfolio = path_1.default.join(relayDir, 'portfolio');
|
|
142
|
+
if (fs_1.default.existsSync(legacyPortfolio) && fs_1.default.statSync(legacyPortfolio).isDirectory() && !fs_1.default.existsSync(newPortfolio)) {
|
|
143
|
+
fs_1.default.renameSync(legacyPortfolio, newPortfolio);
|
|
144
|
+
result.portfolio = true;
|
|
145
|
+
}
|
|
146
|
+
// 3. 로컬 레거시 슬래시 커맨드 제거
|
|
147
|
+
const detected = (0, ai_tools_js_1.detectAgentCLIs)(projectPath);
|
|
148
|
+
for (const tool of detected) {
|
|
149
|
+
const cmdDir = path_1.default.join(projectPath, tool.skillsDir, 'commands', 'relay');
|
|
150
|
+
if (!fs_1.default.existsSync(cmdDir))
|
|
151
|
+
continue;
|
|
152
|
+
for (const cmdId of LEGACY_LOCAL_COMMAND_IDS) {
|
|
153
|
+
const cmdPath = path_1.default.join(cmdDir, `${cmdId}.md`);
|
|
154
|
+
if (fs_1.default.existsSync(cmdPath)) {
|
|
155
|
+
fs_1.default.unlinkSync(cmdPath);
|
|
156
|
+
result.localCommandsCleaned++;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// 디렉토리가 비었으면 삭제
|
|
160
|
+
const remaining = fs_1.default.readdirSync(cmdDir);
|
|
161
|
+
if (remaining.length === 0) {
|
|
162
|
+
fs_1.default.rmdirSync(cmdDir);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
93
167
|
function registerInit(program) {
|
|
94
168
|
program
|
|
95
169
|
.command('init')
|
|
@@ -99,6 +173,42 @@ function registerInit(program) {
|
|
|
99
173
|
.action(async (opts) => {
|
|
100
174
|
const json = program.opts().json ?? false;
|
|
101
175
|
const projectPath = process.cwd();
|
|
176
|
+
// ── 0. 레거시 마이그레이션 ──
|
|
177
|
+
const legacy = detectLegacy(projectPath);
|
|
178
|
+
let migrated = false;
|
|
179
|
+
if (legacy.hasLegacy) {
|
|
180
|
+
if (json) {
|
|
181
|
+
// JSON 모드: 자동 마이그레이션
|
|
182
|
+
const migrationResult = runMigration(projectPath);
|
|
183
|
+
migrated = migrationResult.relayYaml || migrationResult.portfolio || migrationResult.localCommandsCleaned > 0;
|
|
184
|
+
}
|
|
185
|
+
else if (process.stdin.isTTY) {
|
|
186
|
+
console.log('\n \x1b[33m⚠ 레거시 구조 감지\x1b[0m\n');
|
|
187
|
+
for (const d of legacy.details) {
|
|
188
|
+
console.log(` ${d}`);
|
|
189
|
+
}
|
|
190
|
+
console.log();
|
|
191
|
+
const { confirm } = await import('@inquirer/prompts');
|
|
192
|
+
const doMigrate = await confirm({ message: '마이그레이션할까요?', default: true });
|
|
193
|
+
if (doMigrate) {
|
|
194
|
+
const migrationResult = runMigration(projectPath);
|
|
195
|
+
migrated = true;
|
|
196
|
+
console.log(`\n \x1b[32m✓ 마이그레이션 완료\x1b[0m`);
|
|
197
|
+
if (migrationResult.relayYaml)
|
|
198
|
+
console.log(' relay.yaml → .relay/relay.yaml');
|
|
199
|
+
if (migrationResult.portfolio)
|
|
200
|
+
console.log(' portfolio/ → .relay/portfolio/');
|
|
201
|
+
if (migrationResult.localCommandsCleaned > 0)
|
|
202
|
+
console.log(` 레거시 로컬 커맨드 ${migrationResult.localCommandsCleaned}개 제거`);
|
|
203
|
+
console.log();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
// 비TTY, 비JSON: 자동 마이그레이션
|
|
208
|
+
const migrationResult = runMigration(projectPath);
|
|
209
|
+
migrated = migrationResult.relayYaml || migrationResult.portfolio || migrationResult.localCommandsCleaned > 0;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
102
212
|
const detected = (0, ai_tools_js_1.detectAgentCLIs)(projectPath);
|
|
103
213
|
const detectedIds = new Set(detected.map((t) => t.value));
|
|
104
214
|
const isBuilder = isTeamProject(projectPath);
|
|
@@ -183,6 +293,7 @@ function registerInit(program) {
|
|
|
183
293
|
console.log(JSON.stringify({
|
|
184
294
|
status: 'ok',
|
|
185
295
|
mode: isBuilder ? 'builder' : 'user',
|
|
296
|
+
migrated,
|
|
186
297
|
global: {
|
|
187
298
|
status: globalStatus,
|
|
188
299
|
path: (0, command_adapter_js_1.getGlobalCommandDir)(),
|
package/dist/commands/publish.js
CHANGED
|
@@ -12,6 +12,10 @@ const tar_1 = require("tar");
|
|
|
12
12
|
const config_js_1 = require("../lib/config.js");
|
|
13
13
|
const VALID_DIRS = ['skills', 'agents', 'rules', 'commands'];
|
|
14
14
|
const IMAGE_EXTS = ['.png', '.jpg', '.jpeg', '.webp'];
|
|
15
|
+
/** 개별 포트폴리오 이미지 최대 크기 (2 MB) */
|
|
16
|
+
const MAX_IMAGE_SIZE = 2 * 1024 * 1024;
|
|
17
|
+
/** 전체 업로드 최대 크기 (10 MB) */
|
|
18
|
+
const MAX_TOTAL_UPLOAD_SIZE = 10 * 1024 * 1024;
|
|
15
19
|
function parseRelayYaml(content) {
|
|
16
20
|
const raw = js_yaml_1.default.load(content) ?? {};
|
|
17
21
|
const tags = Array.isArray(raw.tags)
|
|
@@ -93,15 +97,15 @@ function collectPortfolio(teamDir, yamlPortfolio) {
|
|
|
93
97
|
return fs_1.default.existsSync(absPath);
|
|
94
98
|
});
|
|
95
99
|
}
|
|
96
|
-
// Auto-scan
|
|
97
|
-
const portfolioDir = path_1.default.join(teamDir, 'portfolio');
|
|
100
|
+
// Auto-scan .relay/portfolio/
|
|
101
|
+
const portfolioDir = path_1.default.join(teamDir, '.relay', 'portfolio');
|
|
98
102
|
if (!fs_1.default.existsSync(portfolioDir))
|
|
99
103
|
return [];
|
|
100
104
|
const files = fs_1.default.readdirSync(portfolioDir)
|
|
101
105
|
.filter((f) => IMAGE_EXTS.some((ext) => f.toLowerCase().endsWith(ext)))
|
|
102
106
|
.sort();
|
|
103
107
|
return files.map((f) => ({
|
|
104
|
-
path: path_1.default.join('portfolio', f),
|
|
108
|
+
path: path_1.default.join('.relay', 'portfolio', f),
|
|
105
109
|
title: path_1.default.basename(f, path_1.default.extname(f)).replace(/[-_]/g, ' '),
|
|
106
110
|
}));
|
|
107
111
|
}
|
|
@@ -140,13 +144,23 @@ async function publishToApi(token, tarPath, metadata, teamDir, portfolioEntries)
|
|
|
140
144
|
const form = new FormData();
|
|
141
145
|
form.append('package', blob, `${metadata.slug}-${metadata.version}.tar.gz`);
|
|
142
146
|
form.append('metadata', JSON.stringify(metadata));
|
|
143
|
-
// Attach portfolio images
|
|
147
|
+
// Attach portfolio images (with size validation)
|
|
144
148
|
if (portfolioEntries.length > 0) {
|
|
145
149
|
const portfolioMeta = [];
|
|
150
|
+
let totalImageSize = 0;
|
|
146
151
|
for (let i = 0; i < portfolioEntries.length; i++) {
|
|
147
152
|
const entry = portfolioEntries[i];
|
|
148
153
|
const absPath = path_1.default.resolve(teamDir, entry.path);
|
|
149
154
|
const imgBuffer = fs_1.default.readFileSync(absPath);
|
|
155
|
+
if (imgBuffer.length > MAX_IMAGE_SIZE) {
|
|
156
|
+
const sizeMB = (imgBuffer.length / 1024 / 1024).toFixed(1);
|
|
157
|
+
throw new Error(`포트폴리오 이미지 '${path_1.default.basename(entry.path)}'이(가) 너무 큽니다 (${sizeMB}MB). 최대 ${MAX_IMAGE_SIZE / 1024 / 1024}MB까지 허용됩니다.`);
|
|
158
|
+
}
|
|
159
|
+
totalImageSize += imgBuffer.length;
|
|
160
|
+
if (totalImageSize > MAX_TOTAL_UPLOAD_SIZE) {
|
|
161
|
+
const totalMB = (totalImageSize / 1024 / 1024).toFixed(1);
|
|
162
|
+
throw new Error(`포트폴리오 이미지 총 크기가 너무 큽니다 (${totalMB}MB). 최대 ${MAX_TOTAL_UPLOAD_SIZE / 1024 / 1024}MB까지 허용됩니다.`);
|
|
163
|
+
}
|
|
150
164
|
const ext = path_1.default.extname(entry.path).slice(1) || 'png';
|
|
151
165
|
const mimeType = ext === 'jpg' || ext === 'jpeg' ? 'image/jpeg' : ext === 'webp' ? 'image/webp' : 'image/png';
|
|
152
166
|
const imgBlob = new Blob([imgBuffer], { type: mimeType });
|
|
@@ -179,14 +193,15 @@ function registerPublish(program) {
|
|
|
179
193
|
.action(async (opts) => {
|
|
180
194
|
const json = program.opts().json ?? false;
|
|
181
195
|
const teamDir = process.cwd();
|
|
182
|
-
const
|
|
196
|
+
const relayDir = path_1.default.join(teamDir, '.relay');
|
|
197
|
+
const relayYamlPath = path_1.default.join(relayDir, 'relay.yaml');
|
|
183
198
|
const isTTY = Boolean(process.stdin.isTTY) && !json;
|
|
184
|
-
// Check relay.yaml exists
|
|
199
|
+
// Check .relay/relay.yaml exists
|
|
185
200
|
if (!fs_1.default.existsSync(relayYamlPath)) {
|
|
186
201
|
if (!isTTY) {
|
|
187
202
|
console.error(JSON.stringify({
|
|
188
203
|
error: 'NOT_INITIALIZED',
|
|
189
|
-
message: 'relay.yaml이 없습니다. 먼저 `relay
|
|
204
|
+
message: '.relay/relay.yaml이 없습니다. 먼저 `relay create`를 실행하세요.',
|
|
190
205
|
}));
|
|
191
206
|
process.exit(1);
|
|
192
207
|
}
|
|
@@ -195,7 +210,7 @@ function registerPublish(program) {
|
|
|
195
210
|
const dirName = path_1.default.basename(teamDir);
|
|
196
211
|
const defaultSlug = dirName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
197
212
|
console.error('\n\x1b[36m릴레이 팀 패키지를 초기화합니다.\x1b[0m');
|
|
198
|
-
console.error('relay.yaml을 생성하기 위해 몇 가지 정보를 입력해주세요.\n');
|
|
213
|
+
console.error('.relay/relay.yaml을 생성하기 위해 몇 가지 정보를 입력해주세요.\n');
|
|
199
214
|
const name = await promptInput({
|
|
200
215
|
message: '팀 이름:',
|
|
201
216
|
default: dirName,
|
|
@@ -222,7 +237,7 @@ function registerPublish(program) {
|
|
|
222
237
|
});
|
|
223
238
|
console.error('\n\x1b[2m💡 프로필에 연락처를 설정하면 설치 시 명함이 전달됩니다: relayax.com/dashboard/edit\x1b[0m');
|
|
224
239
|
if (visibility === 'invite-only') {
|
|
225
|
-
console.error('\x1b[2m💡 invite-only 팀은
|
|
240
|
+
console.error('\x1b[2m💡 invite-only 팀은 웹 대시보드에서 사용자를 초대하세요: relayax.com/dashboard\x1b[0m');
|
|
226
241
|
}
|
|
227
242
|
console.error('');
|
|
228
243
|
const tags = tagsRaw
|
|
@@ -237,8 +252,9 @@ function registerPublish(program) {
|
|
|
237
252
|
tags,
|
|
238
253
|
visibility,
|
|
239
254
|
};
|
|
255
|
+
fs_1.default.mkdirSync(relayDir, { recursive: true });
|
|
240
256
|
fs_1.default.writeFileSync(relayYamlPath, js_yaml_1.default.dump(yamlData, { lineWidth: 120 }), 'utf-8');
|
|
241
|
-
console.error(`\n\x1b[32m✓ relay.yaml이 생성되었습니다.\x1b[0m\n`);
|
|
257
|
+
console.error(`\n\x1b[32m✓ .relay/relay.yaml이 생성되었습니다.\x1b[0m\n`);
|
|
242
258
|
}
|
|
243
259
|
// Parse relay.yaml
|
|
244
260
|
const yamlContent = fs_1.default.readFileSync(relayYamlPath, 'utf-8');
|
package/dist/commands/status.js
CHANGED
|
@@ -48,7 +48,7 @@ function registerStatus(program) {
|
|
|
48
48
|
hasLocal = command_adapter_js_1.BUILDER_COMMANDS.some((cmd) => fs_1.default.existsSync(path_1.default.join(localDir, `${cmd.id}.md`)));
|
|
49
49
|
}
|
|
50
50
|
// 3. 팀 정보
|
|
51
|
-
const relayYamlPath = path_1.default.join(projectPath, 'relay.yaml');
|
|
51
|
+
const relayYamlPath = path_1.default.join(projectPath, '.relay', 'relay.yaml');
|
|
52
52
|
let team = null;
|
|
53
53
|
if (fs_1.default.existsSync(relayYamlPath)) {
|
|
54
54
|
try {
|
|
@@ -88,7 +88,7 @@ exports.USER_COMMANDS = [
|
|
|
88
88
|
- 설치 결과를 확인합니다 (설치된 파일 수, 사용 가능한 커맨드 목록).
|
|
89
89
|
|
|
90
90
|
### 2. 의존성 확인 및 설치
|
|
91
|
-
설치된 팀의 relay.yaml에 \`requires\` 섹션이 있으면 각 항목을 확인하고 처리합니다:
|
|
91
|
+
설치된 팀의 .relay/relay.yaml에 \`requires\` 섹션이 있으면 각 항목을 확인하고 처리합니다:
|
|
92
92
|
|
|
93
93
|
- **cli**: \`which <name>\`으로 확인 → 없으면 install 명령 실행 또는 안내
|
|
94
94
|
- **npm**: \`npm list <package>\`로 확인 → 없으면 \`npm install\`
|
|
@@ -206,7 +206,7 @@ ${LOGIN_JIT_GUIDE}
|
|
|
206
206
|
### 2. 팀 구조 분석
|
|
207
207
|
- skills/, agents/, rules/, commands/ 디렉토리를 탐색합니다.
|
|
208
208
|
- 각 파일의 이름과 description을 추출합니다.
|
|
209
|
-
- relay.yaml이 있으면 읽고, 없으면 사용자에게 팀 정보(name, slug, description, tags)를 물어보고 생성합니다.
|
|
209
|
+
- .relay/relay.yaml이 있으면 읽고, 없으면 사용자에게 팀 정보(name, slug, description, tags)를 물어보고 생성합니다.
|
|
210
210
|
|
|
211
211
|
### 3. 포트폴리오 생성
|
|
212
212
|
|
|
@@ -219,14 +219,14 @@ ${LOGIN_JIT_GUIDE}
|
|
|
219
219
|
- Rules 목록
|
|
220
220
|
- 비시각적 팀의 경우 기술 스택이나 데이터 종류 등 추가 정보
|
|
221
221
|
- 생성된 HTML을 Playwright로 스크린샷 캡처합니다. (gstack 또는 webapp-testing 스킬 활용)
|
|
222
|
-
- 결과 PNG를
|
|
222
|
+
- 결과 PNG를 ./.relay/portfolio/team-overview.png에 저장합니다.
|
|
223
223
|
|
|
224
224
|
#### Layer 2: 결과물 쇼케이스 (선택)
|
|
225
|
-
- output/, results/, examples
|
|
225
|
+
- output/, results/, examples/ 디렉토리를 스캔합니다.
|
|
226
226
|
- 발견된 결과물(PNG, JPG, HTML, PDF)을 사용자에게 보여줍니다.
|
|
227
227
|
- HTML 파일은 Playwright 스크린샷으로 변환합니다.
|
|
228
228
|
- 사용자가 포트폴리오에 포함할 항목을 선택합니다.
|
|
229
|
-
- 선택된 이미지를
|
|
229
|
+
- 선택된 이미지를 ./.relay/portfolio/에 저장합니다.
|
|
230
230
|
|
|
231
231
|
### 4. 메타데이터 생성
|
|
232
232
|
- description: skills 내용 기반으로 자동 생성합니다.
|
|
@@ -234,8 +234,8 @@ ${LOGIN_JIT_GUIDE}
|
|
|
234
234
|
- tags: 팀 특성에 맞는 태그를 추천합니다.
|
|
235
235
|
- 사용자에게 확인: "이대로 배포할까요?"
|
|
236
236
|
|
|
237
|
-
### 5. relay.yaml 업데이트
|
|
238
|
-
- 생성/수정된 메타데이터와 포트폴리오 경로를 relay.yaml에 반영합니다.
|
|
237
|
+
### 5. .relay/relay.yaml 업데이트
|
|
238
|
+
- 생성/수정된 메타데이터와 포트폴리오 경로를 .relay/relay.yaml에 반영합니다.
|
|
239
239
|
|
|
240
240
|
### 6. 배포
|
|
241
241
|
- \`relay publish\` 명령어를 실행합니다.
|
|
@@ -247,7 +247,7 @@ ${LOGIN_JIT_GUIDE}
|
|
|
247
247
|
→ 팀 구조 분석: skills 3개, commands 5개, agents 2개
|
|
248
248
|
→ Layer 1: 팀 구성 시각화 HTML 생성 → 스크린샷 캡처
|
|
249
249
|
→ Layer 2: output/ 스캔 → "카드뉴스 예시.png, PDF 보고서.png 발견. 포트폴리오에 포함할까요?"
|
|
250
|
-
→ 사용자 확인 후 relay.yaml 업데이트
|
|
250
|
+
→ 사용자 확인 후 .relay/relay.yaml 업데이트
|
|
251
251
|
→ relay publish 실행
|
|
252
252
|
→ "배포 완료! URL: https://relayax.com/teams/contents-team"`,
|
|
253
253
|
},
|
|
@@ -0,0 +1,75 @@
|
|
|
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.API_URL = void 0;
|
|
7
|
+
exports.getInstallPath = getInstallPath;
|
|
8
|
+
exports.ensureRelayDir = ensureRelayDir;
|
|
9
|
+
exports.loadToken = loadToken;
|
|
10
|
+
exports.saveToken = saveToken;
|
|
11
|
+
exports.loadInstalled = loadInstalled;
|
|
12
|
+
exports.saveInstalled = saveInstalled;
|
|
13
|
+
const fs_1 = __importDefault(require("fs"));
|
|
14
|
+
const path_1 = __importDefault(require("path"));
|
|
15
|
+
const os_1 = __importDefault(require("os"));
|
|
16
|
+
const ai_tools_js_1 = require("./ai-tools.js");
|
|
17
|
+
exports.API_URL = 'https://relayax.com';
|
|
18
|
+
const RELAY_DIR = path_1.default.join(os_1.default.homedir(), '.relay');
|
|
19
|
+
const INSTALLED_FILE = path_1.default.join(RELAY_DIR, 'installed.json');
|
|
20
|
+
/**
|
|
21
|
+
* 설치 경로를 결정한다.
|
|
22
|
+
* 1. --path 옵션이 있으면 그대로 사용
|
|
23
|
+
* 2. 에이전트 CLI 자동 감지 → 감지된 경로 사용
|
|
24
|
+
* 3. 감지 안 되면 현재 디렉토리에 직접 설치
|
|
25
|
+
*/
|
|
26
|
+
function getInstallPath(override) {
|
|
27
|
+
if (override) {
|
|
28
|
+
const resolved = override.startsWith('~')
|
|
29
|
+
? path_1.default.join(os_1.default.homedir(), override.slice(1))
|
|
30
|
+
: path_1.default.resolve(override);
|
|
31
|
+
return resolved;
|
|
32
|
+
}
|
|
33
|
+
const cwd = process.cwd();
|
|
34
|
+
const detected = (0, ai_tools_js_1.detectAgentCLIs)(cwd);
|
|
35
|
+
if (detected.length >= 1) {
|
|
36
|
+
return path_1.default.join(cwd, detected[0].skillsDir);
|
|
37
|
+
}
|
|
38
|
+
return cwd;
|
|
39
|
+
}
|
|
40
|
+
function ensureRelayDir() {
|
|
41
|
+
if (!fs_1.default.existsSync(RELAY_DIR)) {
|
|
42
|
+
fs_1.default.mkdirSync(RELAY_DIR, { recursive: true });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function loadToken() {
|
|
46
|
+
const tokenFile = path_1.default.join(RELAY_DIR, 'token');
|
|
47
|
+
if (!fs_1.default.existsSync(tokenFile))
|
|
48
|
+
return undefined;
|
|
49
|
+
try {
|
|
50
|
+
return fs_1.default.readFileSync(tokenFile, 'utf-8').trim() || undefined;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function saveToken(token) {
|
|
57
|
+
ensureRelayDir();
|
|
58
|
+
fs_1.default.writeFileSync(path_1.default.join(RELAY_DIR, 'token'), token);
|
|
59
|
+
}
|
|
60
|
+
function loadInstalled() {
|
|
61
|
+
if (!fs_1.default.existsSync(INSTALLED_FILE)) {
|
|
62
|
+
return {};
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const raw = fs_1.default.readFileSync(INSTALLED_FILE, 'utf-8');
|
|
66
|
+
return JSON.parse(raw);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return {};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function saveInstalled(registry) {
|
|
73
|
+
ensureRelayDir();
|
|
74
|
+
fs_1.default.writeFileSync(INSTALLED_FILE, JSON.stringify(registry, null, 2));
|
|
75
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "relayax-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.94",
|
|
4
4
|
"description": "RelayAX Agent Team Marketplace CLI - Install and manage agent teams",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@inquirer/prompts": "^8.3.2",
|
|
37
37
|
"commander": "^13.1.0",
|
|
38
|
+
"form-data": "^4.0.5",
|
|
38
39
|
"js-yaml": "^4.1.1",
|
|
39
40
|
"tar": "^7.4.0"
|
|
40
41
|
},
|