relayax-cli 0.4.16 → 0.4.18
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/install.js +25 -30
- package/dist/commands/publish.d.ts +1 -0
- package/dist/commands/publish.js +30 -49
- package/dist/lib/git-operations.js +5 -1
- package/dist/lib/preamble.js +11 -2
- package/dist/mcp/server.js +92 -11
- package/dist/prompts/create.md +3 -3
- package/package.json +1 -1
package/dist/commands/install.js
CHANGED
|
@@ -132,13 +132,13 @@ function registerInstall(program) {
|
|
|
132
132
|
}
|
|
133
133
|
process.exit(1);
|
|
134
134
|
}
|
|
135
|
-
const claimRes = await fetch(`${config_js_1.API_URL}/api/agents/${
|
|
135
|
+
const claimRes = await fetch(`${config_js_1.API_URL}/api/agents/${parsed.name}/claim-access`, {
|
|
136
136
|
method: 'POST',
|
|
137
137
|
headers: {
|
|
138
138
|
'Content-Type': 'application/json',
|
|
139
139
|
Authorization: `Bearer ${token}`,
|
|
140
140
|
},
|
|
141
|
-
body: JSON.stringify({ code: _opts.code }),
|
|
141
|
+
body: JSON.stringify({ code: _opts.code, owner: parsed.owner }),
|
|
142
142
|
signal: AbortSignal.timeout(10000),
|
|
143
143
|
});
|
|
144
144
|
if (!claimRes.ok) {
|
|
@@ -240,38 +240,33 @@ function registerInstall(program) {
|
|
|
240
240
|
process.exit(1);
|
|
241
241
|
}
|
|
242
242
|
}
|
|
243
|
-
// 3. Download package
|
|
243
|
+
// 3. Download package via git clone
|
|
244
244
|
const requestedVersion = versionMatch ? versionMatch[2] : undefined;
|
|
245
|
-
if (resolvedAgent.git_url) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
await (0, storage_js_1.clonePackage)(gitUrl, agentDir, requestedVersion);
|
|
250
|
-
}
|
|
251
|
-
else {
|
|
252
|
-
// Legacy tar.gz path (retry once if signed URL expired)
|
|
253
|
-
let tarPath;
|
|
254
|
-
try {
|
|
255
|
-
tarPath = await (0, storage_js_1.downloadPackage)(resolvedAgent.package_url, tempDir);
|
|
245
|
+
if (!resolvedAgent.git_url) {
|
|
246
|
+
const errMsg = '이 에이전트는 재publish가 필요합니다. 빌더에게 문의하세요.';
|
|
247
|
+
if (json) {
|
|
248
|
+
console.log(JSON.stringify({ error: 'NO_GIT_URL', message: errMsg }));
|
|
256
249
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
if (dlMsg.includes('403') || dlMsg.includes('expired')) {
|
|
260
|
-
if (!json) {
|
|
261
|
-
console.error('\x1b[33m⚙ 다운로드 URL 만료, 재시도 중...\x1b[0m');
|
|
262
|
-
}
|
|
263
|
-
resolvedAgent = await (0, api_js_1.fetchAgentInfo)(slug);
|
|
264
|
-
tarPath = await (0, storage_js_1.downloadPackage)(resolvedAgent.package_url, tempDir);
|
|
265
|
-
}
|
|
266
|
-
else {
|
|
267
|
-
throw dlErr;
|
|
268
|
-
}
|
|
250
|
+
else {
|
|
251
|
+
console.error(`\x1b[31m✖ ${errMsg}\x1b[0m`);
|
|
269
252
|
}
|
|
270
|
-
|
|
271
|
-
|
|
253
|
+
process.exit(1);
|
|
254
|
+
}
|
|
255
|
+
(0, git_operations_js_1.checkGitInstalled)();
|
|
256
|
+
const gitUrl = (0, git_operations_js_1.buildGitUrl)(resolvedAgent.git_url, { code: _opts.code });
|
|
257
|
+
await (0, storage_js_1.clonePackage)(gitUrl, agentDir, requestedVersion);
|
|
258
|
+
// Verify clone has actual files (not just .git)
|
|
259
|
+
const clonedEntries = fs_1.default.readdirSync(agentDir).filter((f) => f !== '.git');
|
|
260
|
+
if (clonedEntries.length === 0) {
|
|
261
|
+
fs_1.default.rmSync(agentDir, { recursive: true, force: true });
|
|
262
|
+
const errMsg = '에이전트 패키지가 비어있습니다. 빌더에게 재publish를 요청하세요.';
|
|
263
|
+
if (json) {
|
|
264
|
+
console.log(JSON.stringify({ error: 'EMPTY_PACKAGE', message: errMsg }));
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
console.error(`\x1b[31m✖ ${errMsg}\x1b[0m`);
|
|
272
268
|
}
|
|
273
|
-
|
|
274
|
-
await (0, storage_js_1.extractPackage)(tarPath, agentDir);
|
|
269
|
+
process.exit(1);
|
|
275
270
|
}
|
|
276
271
|
// 4.5. Inject preamble (update check) into SKILL.md and commands
|
|
277
272
|
(0, preamble_js_1.injectPreambleToAgent)(agentDir, slug);
|
package/dist/commands/publish.js
CHANGED
|
@@ -746,13 +746,13 @@ function registerPublish(program) {
|
|
|
746
746
|
console.error(`업로드 중...`);
|
|
747
747
|
}
|
|
748
748
|
const result = await publishToApi(token, tarPath, metadata);
|
|
749
|
-
// Git push: commit and push to git server (
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
if (
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
749
|
+
// Git push: commit and push to git server (required)
|
|
750
|
+
const gitUrl = result.git_url;
|
|
751
|
+
if (gitUrl) {
|
|
752
|
+
if (!json) {
|
|
753
|
+
console.error('git 저장소에 푸시 중...');
|
|
754
|
+
}
|
|
755
|
+
try {
|
|
756
756
|
const isFirstPublish = !result.is_update;
|
|
757
757
|
if (isFirstPublish) {
|
|
758
758
|
await (0, git_operations_js_1.gitPublishInit)(relayDir, gitUrl, config.version);
|
|
@@ -761,12 +761,16 @@ function registerPublish(program) {
|
|
|
761
761
|
await (0, git_operations_js_1.gitPublishUpdate)(relayDir, gitUrl, config.version);
|
|
762
762
|
}
|
|
763
763
|
}
|
|
764
|
-
|
|
765
|
-
catch (gitPushErr) {
|
|
766
|
-
// Git push failure is non-fatal — tar.gz upload already succeeded
|
|
767
|
-
if (!json) {
|
|
764
|
+
catch (gitPushErr) {
|
|
768
765
|
const gpMsg = gitPushErr instanceof Error ? gitPushErr.message : String(gitPushErr);
|
|
769
|
-
|
|
766
|
+
if (json) {
|
|
767
|
+
console.log(JSON.stringify({ error: 'GIT_PUSH_FAILED', message: `git push 실패: ${gpMsg}` }));
|
|
768
|
+
}
|
|
769
|
+
else {
|
|
770
|
+
console.error(`\x1b[31m✖ git push 실패: ${gpMsg}\x1b[0m`);
|
|
771
|
+
console.error('\x1b[33m 재시도하려면 relay publish를 다시 실행하세요.\x1b[0m');
|
|
772
|
+
}
|
|
773
|
+
process.exit(1);
|
|
770
774
|
}
|
|
771
775
|
}
|
|
772
776
|
// Update entry command preamble with scoped slug from server (non-fatal)
|
|
@@ -784,15 +788,7 @@ function registerPublish(program) {
|
|
|
784
788
|
// preamble update is best-effort — publish already succeeded
|
|
785
789
|
}
|
|
786
790
|
if (json) {
|
|
787
|
-
// Enrich JSON output with plugin_url if git_url available
|
|
788
791
|
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
792
|
jsonResult.platforms = generatedPlatforms;
|
|
797
793
|
console.log(JSON.stringify(jsonResult));
|
|
798
794
|
}
|
|
@@ -803,50 +799,35 @@ function registerPublish(program) {
|
|
|
803
799
|
// Build share block
|
|
804
800
|
{
|
|
805
801
|
const detailSlug = result.slug.startsWith('@') ? result.slug.slice(1) : result.slug;
|
|
806
|
-
const accessCode = result.access_code;
|
|
807
|
-
const gitUrl = result.git_url
|
|
808
|
-
//
|
|
802
|
+
const accessCode = result.access_code ?? null;
|
|
803
|
+
// const gitUrl = (result as unknown as Record<string, unknown>).git_url as string | undefined // plugin disabled
|
|
804
|
+
// npx turnkey install command (works everywhere, no pre-install needed)
|
|
809
805
|
const visibility = config.visibility ?? 'public';
|
|
810
|
-
let
|
|
806
|
+
let npxInstallCmd;
|
|
811
807
|
if (visibility === 'internal' && accessCode) {
|
|
812
|
-
|
|
808
|
+
npxInstallCmd = `npx relayax-cli install ${result.slug} --join-code ${accessCode}`;
|
|
813
809
|
}
|
|
814
810
|
else if (visibility === 'private' && accessCode) {
|
|
815
|
-
|
|
811
|
+
npxInstallCmd = `npx relayax-cli install ${result.slug} --code ${accessCode}`;
|
|
816
812
|
}
|
|
817
813
|
else {
|
|
818
|
-
|
|
814
|
+
npxInstallCmd = `npx relayax-cli install ${result.slug}`;
|
|
819
815
|
}
|
|
820
|
-
//
|
|
821
|
-
|
|
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`);
|
|
816
|
+
// ── 설치 방법 (터미널 출력) ──
|
|
817
|
+
console.log(`\n \x1b[1m설치 방법\x1b[0m \x1b[90m(Claude Code, Cursor, Codex 등 모든 에이전트)\x1b[0m`);
|
|
825
818
|
console.log(` ┌─`);
|
|
826
|
-
console.log(` │ ${
|
|
819
|
+
console.log(` │ ${npxInstallCmd}`);
|
|
827
820
|
console.log(` └─`);
|
|
828
|
-
|
|
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}`);
|
|
821
|
+
console.log(`\n \x1b[90m소개:\x1b[0m https://relayax.com/@${detailSlug}`);
|
|
838
822
|
// ── 공유 텍스트 (코드블록, 그대로 복붙) ──
|
|
839
823
|
if (isTTY) {
|
|
840
824
|
const shareBlock = [
|
|
841
825
|
`[${config.name}] 설치하기`,
|
|
842
826
|
``,
|
|
843
|
-
|
|
844
|
-
|
|
827
|
+
npxInstallCmd,
|
|
828
|
+
``,
|
|
829
|
+
`소개: https://relayax.com/@${detailSlug}`,
|
|
845
830
|
];
|
|
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
831
|
const maxLen = Math.max(...shareBlock.map((l) => l.length));
|
|
851
832
|
const border = '─'.repeat(maxLen + 2);
|
|
852
833
|
console.log(`\n \x1b[90m┌${border}┐\x1b[0m`);
|
|
@@ -45,7 +45,11 @@ function gitClone(url, destDir, opts) {
|
|
|
45
45
|
args.push('--depth', String(opts.depth));
|
|
46
46
|
}
|
|
47
47
|
args.push(url, destDir);
|
|
48
|
-
(0, child_process_1.execFileSync)('git', args, {
|
|
48
|
+
(0, child_process_1.execFileSync)('git', args, {
|
|
49
|
+
stdio: 'pipe',
|
|
50
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
|
51
|
+
timeout: 30000,
|
|
52
|
+
});
|
|
49
53
|
}
|
|
50
54
|
function gitAdd(dir, files = '.') {
|
|
51
55
|
(0, child_process_1.execFileSync)('git', ['add', files], { cwd: dir, stdio: 'pipe' });
|
package/dist/lib/preamble.js
CHANGED
|
@@ -37,12 +37,21 @@ else
|
|
|
37
37
|
DEVICE_HASH="unknown"
|
|
38
38
|
fi
|
|
39
39
|
|
|
40
|
-
#
|
|
40
|
+
# Read relay token (for user identification in usage ping)
|
|
41
|
+
_RELAY_TOKEN=""
|
|
42
|
+
if [ -f "$HOME/.relay/token.json" ]; then
|
|
43
|
+
_RELAY_TOKEN=$(grep -o '"access_token":"[^"]*"' "$HOME/.relay/token.json" 2>/dev/null | head -1 | cut -d'"' -f4)
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# Usage ping (with user identity if logged in)
|
|
41
47
|
if command -v relay &>/dev/null; then
|
|
42
48
|
relay ping "${slug}" --quiet 2>/dev/null &
|
|
43
49
|
elif command -v curl &>/dev/null; then
|
|
44
|
-
|
|
50
|
+
_AUTH_HEADER=""
|
|
51
|
+
[ -n "$_RELAY_TOKEN" ] && _AUTH_HEADER="-H \\"Authorization: Bearer $_RELAY_TOKEN\\""
|
|
52
|
+
eval curl -sf --max-time 5 -X POST "${apiUrl}/api/agents/${agentSlug}/ping" \\
|
|
45
53
|
-H "Content-Type: application/json" \\
|
|
54
|
+
$_AUTH_HEADER \\
|
|
46
55
|
-d "{\\"device_hash\\":\\"$DEVICE_HASH\\",\\"slug\\":\\"${slug}\\"}" \\
|
|
47
56
|
2>/dev/null &
|
|
48
57
|
fi
|
package/dist/mcp/server.js
CHANGED
|
@@ -126,18 +126,16 @@ function createMcpServer() {
|
|
|
126
126
|
const tempDir = (0, storage_js_1.makeTempDir)();
|
|
127
127
|
try {
|
|
128
128
|
const agentDir = path_1.default.join(projectPath, '.relay', 'agents', parsed.owner, parsed.name);
|
|
129
|
-
if (agent.git_url) {
|
|
130
|
-
|
|
131
|
-
(0, git_operations_js_1.checkGitInstalled)();
|
|
132
|
-
await (0, storage_js_1.clonePackage)(agent.git_url, agentDir);
|
|
129
|
+
if (!agent.git_url) {
|
|
130
|
+
return { content: [jsonText({ error: 'NO_GIT_URL', message: '이 에이전트는 재publish가 필요합니다. 빌더에게 문의하세요.' })], isError: true };
|
|
133
131
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
fs_1.default.
|
|
140
|
-
|
|
132
|
+
(0, git_operations_js_1.checkGitInstalled)();
|
|
133
|
+
await (0, storage_js_1.clonePackage)(agent.git_url, agentDir);
|
|
134
|
+
// Verify clone has actual files
|
|
135
|
+
const clonedEntries = fs_1.default.readdirSync(agentDir).filter((f) => f !== '.git');
|
|
136
|
+
if (clonedEntries.length === 0) {
|
|
137
|
+
fs_1.default.rmSync(agentDir, { recursive: true, force: true });
|
|
138
|
+
return { content: [jsonText({ error: 'EMPTY_PACKAGE', message: '에이전트 패키지가 비어있습니다. 빌더에게 재publish를 요청하세요.' })], isError: true };
|
|
141
139
|
}
|
|
142
140
|
(0, preamble_js_1.injectPreambleToAgent)(agentDir, fullSlug);
|
|
143
141
|
const installed = (0, config_js_1.loadInstalled)();
|
|
@@ -734,6 +732,89 @@ function createMcpServer() {
|
|
|
734
732
|
return { content: [jsonText({ error: String(err) })], isError: true };
|
|
735
733
|
}
|
|
736
734
|
});
|
|
735
|
+
// ═══ Detail Images — 상세페이지 이미지 관리 ═══
|
|
736
|
+
server.tool('relay_detail_upload', '에이전트 상세페이지 이미지를 업로드합니다. 폴더 내 이미지를 파일명 순으로 정렬하여 업로드합니다 (기존 이미지 전체 교체).', {
|
|
737
|
+
slug: zod_1.z.string().describe('에이전트 slug'),
|
|
738
|
+
path: zod_1.z.string().describe('이미지가 있는 폴더 경로 (PNG/GIF/JPEG/WebP)'),
|
|
739
|
+
}, async ({ slug, path: dirPath }) => {
|
|
740
|
+
try {
|
|
741
|
+
const token = (0, config_js_1.getValidToken)();
|
|
742
|
+
if (!token)
|
|
743
|
+
return { content: [jsonText({ error: '로그인이 필요합니다. relay login을 먼저 실행하세요.' })], isError: true };
|
|
744
|
+
const absPath = path_1.default.resolve(dirPath);
|
|
745
|
+
if (!fs_1.default.existsSync(absPath) || !fs_1.default.statSync(absPath).isDirectory()) {
|
|
746
|
+
return { content: [jsonText({ error: `폴더를 찾을 수 없습니다: ${absPath}` })], isError: true };
|
|
747
|
+
}
|
|
748
|
+
const imageExts = ['.png', '.jpg', '.jpeg', '.gif', '.webp'];
|
|
749
|
+
const files = fs_1.default.readdirSync(absPath)
|
|
750
|
+
.filter((f) => imageExts.includes(path_1.default.extname(f).toLowerCase()))
|
|
751
|
+
.sort();
|
|
752
|
+
if (files.length === 0) {
|
|
753
|
+
return { content: [jsonText({ error: '폴더에 이미지 파일이 없습니다 (PNG/GIF/JPEG/WebP)' })], isError: true };
|
|
754
|
+
}
|
|
755
|
+
const formData = new FormData();
|
|
756
|
+
for (const file of files) {
|
|
757
|
+
const filePath = path_1.default.join(absPath, file);
|
|
758
|
+
const buffer = fs_1.default.readFileSync(filePath);
|
|
759
|
+
const ext = path_1.default.extname(file).toLowerCase();
|
|
760
|
+
const mimeMap = { '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.webp': 'image/webp' };
|
|
761
|
+
const blob = new Blob([buffer], { type: mimeMap[ext] || 'image/png' });
|
|
762
|
+
formData.append('files', blob, file);
|
|
763
|
+
}
|
|
764
|
+
const res = await fetch(`${config_js_1.API_URL}/api/agents/${slug}/detail-images`, {
|
|
765
|
+
method: 'POST',
|
|
766
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
767
|
+
body: formData,
|
|
768
|
+
});
|
|
769
|
+
if (!res.ok) {
|
|
770
|
+
const body = await res.json().catch(() => ({}));
|
|
771
|
+
return { content: [jsonText({ error: body.message || `업로드 실패 (${res.status})` })], isError: true };
|
|
772
|
+
}
|
|
773
|
+
const result = await res.json();
|
|
774
|
+
const update = await getCliUpdateWarning();
|
|
775
|
+
return { content: [jsonTextWithUpdate({ status: 'uploaded', count: result.count, images: result.detail_images }, update)] };
|
|
776
|
+
}
|
|
777
|
+
catch (err) {
|
|
778
|
+
return { content: [jsonText({ error: String(err) })], isError: true };
|
|
779
|
+
}
|
|
780
|
+
});
|
|
781
|
+
server.tool('relay_detail_list', '에이전트 상세페이지 이미지 목록을 조회합니다', {
|
|
782
|
+
slug: zod_1.z.string().describe('에이전트 slug'),
|
|
783
|
+
}, async ({ slug }) => {
|
|
784
|
+
try {
|
|
785
|
+
const res = await fetch(`${config_js_1.API_URL}/api/agents/${slug}/detail-images`);
|
|
786
|
+
if (!res.ok) {
|
|
787
|
+
return { content: [jsonText({ error: `조회 실패 (${res.status})` })], isError: true };
|
|
788
|
+
}
|
|
789
|
+
const data = await res.json();
|
|
790
|
+
return { content: [jsonText({ detail_images: data.detail_images, count: data.detail_images.length })] };
|
|
791
|
+
}
|
|
792
|
+
catch (err) {
|
|
793
|
+
return { content: [jsonText({ error: String(err) })], isError: true };
|
|
794
|
+
}
|
|
795
|
+
});
|
|
796
|
+
server.tool('relay_detail_clear', '에이전트 상세페이지 이미지를 모두 삭제합니다', {
|
|
797
|
+
slug: zod_1.z.string().describe('에이전트 slug'),
|
|
798
|
+
}, async ({ slug }) => {
|
|
799
|
+
try {
|
|
800
|
+
const token = (0, config_js_1.getValidToken)();
|
|
801
|
+
if (!token)
|
|
802
|
+
return { content: [jsonText({ error: '로그인이 필요합니다.' })], isError: true };
|
|
803
|
+
const res = await fetch(`${config_js_1.API_URL}/api/agents/${slug}/detail-images`, {
|
|
804
|
+
method: 'DELETE',
|
|
805
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
806
|
+
});
|
|
807
|
+
if (!res.ok) {
|
|
808
|
+
const body = await res.json().catch(() => ({}));
|
|
809
|
+
return { content: [jsonText({ error: body.message || `삭제 실패 (${res.status})` })], isError: true };
|
|
810
|
+
}
|
|
811
|
+
const result = await res.json();
|
|
812
|
+
return { content: [jsonText({ status: 'cleared', deleted: result.deleted })] };
|
|
813
|
+
}
|
|
814
|
+
catch (err) {
|
|
815
|
+
return { content: [jsonText({ error: String(err) })], isError: true };
|
|
816
|
+
}
|
|
817
|
+
});
|
|
737
818
|
return server;
|
|
738
819
|
}
|
|
739
820
|
// ─── Start ───
|
package/dist/prompts/create.md
CHANGED
|
@@ -143,10 +143,10 @@ relay.yaml이 없으면 새로 만들고, 있으면 변경사항을 반영합니
|
|
|
143
143
|
1. **배포 결과 요약** — slug, 버전, 공개 범위, URL
|
|
144
144
|
2. **설치 방법** — CLI 출력에 코드블록 형태로 이미 포함되어 있으므로, 그 내용을 사용자에게 안내합니다:
|
|
145
145
|
- CLI: `npx relayax-cli install {slug}`
|
|
146
|
-
- 출력에 `plugin_url`이 있으면 Claude Code Plugin:
|
|
146
|
+
- 출력에 `plugin_url`이 있으면 Claude Code Plugin (**두 명령을 순서대로 각각 실행**):
|
|
147
147
|
```
|
|
148
|
-
/plugin marketplace add {pluginUrl}
|
|
149
|
-
/plugin install {slug}
|
|
148
|
+
① /plugin marketplace add {pluginUrl}
|
|
149
|
+
② /plugin install {slug}
|
|
150
150
|
```
|
|
151
151
|
- 에이전트 소개 페이지 URL
|
|
152
152
|
3. **공유 텍스트** — CLI 출력의 공유 블록(┌─ ... ─┘)을 그대로 안내합니다. 팀에 바로 복붙할 수 있는 코드블록 형태입니다.
|