relayax-cli 0.3.46 → 0.3.49
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/package.js +12 -0
- package/dist/index.js +7 -0
- package/dist/lib/ai-tools.d.ts +16 -0
- package/dist/lib/ai-tools.js +34 -0
- package/dist/lib/guide.js +5 -6
- package/dist/mcp/server.d.ts +3 -0
- package/dist/mcp/server.js +267 -0
- package/dist/prompts/_setup-cli.md +12 -26
- package/dist/prompts/_setup-environment.md +32 -0
- package/dist/prompts/_setup-login.md +12 -15
- package/dist/prompts/index.d.ts +1 -0
- package/dist/prompts/index.js +2 -1
- package/package.json +2 -1
package/dist/commands/package.js
CHANGED
|
@@ -298,6 +298,18 @@ function registerPackage(program) {
|
|
|
298
298
|
});
|
|
299
299
|
}
|
|
300
300
|
}
|
|
301
|
+
// 마운트 경로 스캔 (Cowork/sandbox 환경)
|
|
302
|
+
for (const { tool, basePath } of (0, ai_tools_js_1.detectMountedCLIs)()) {
|
|
303
|
+
const items = (0, ai_tools_js_1.scanMountedItems)(basePath, tool);
|
|
304
|
+
if (items.length > 0) {
|
|
305
|
+
sources.push({
|
|
306
|
+
path: `${basePath}/${tool.skillsDir}`,
|
|
307
|
+
location: 'global',
|
|
308
|
+
name: `${tool.name} (mounted)`,
|
|
309
|
+
items,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
}
|
|
301
313
|
// ~/.relay/agents/ 에 기존 에이전트 패키지가 있는지 스캔
|
|
302
314
|
const globalAgentsDir = path_1.default.join(homeDir ?? os_1.default.homedir(), '.relay', 'agents');
|
|
303
315
|
const existingAgents = [];
|
package/dist/index.js
CHANGED
|
@@ -25,6 +25,7 @@ const access_js_1 = require("./commands/access.js");
|
|
|
25
25
|
const grant_js_1 = require("./commands/grant.js");
|
|
26
26
|
const versions_js_1 = require("./commands/versions.js");
|
|
27
27
|
const diff_js_1 = require("./commands/diff.js");
|
|
28
|
+
const server_js_1 = require("./mcp/server.js");
|
|
28
29
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
29
30
|
const pkg = require('../package.json');
|
|
30
31
|
const program = new commander_1.Command();
|
|
@@ -56,4 +57,10 @@ program
|
|
|
56
57
|
(0, grant_js_1.registerGrant)(program);
|
|
57
58
|
(0, versions_js_1.registerVersions)(program);
|
|
58
59
|
(0, diff_js_1.registerDiff)(program);
|
|
60
|
+
program
|
|
61
|
+
.command('mcp')
|
|
62
|
+
.description('MCP 서버 모드로 실행합니다 (stdio transport)')
|
|
63
|
+
.action(async () => {
|
|
64
|
+
await (0, server_js_1.startMcpServer)();
|
|
65
|
+
});
|
|
59
66
|
program.parse();
|
package/dist/lib/ai-tools.d.ts
CHANGED
|
@@ -12,6 +12,18 @@ export declare const AI_TOOLS: AITool[];
|
|
|
12
12
|
* 프로젝트 디렉토리에서 에이전트 CLI 디렉토리를 감지한다.
|
|
13
13
|
*/
|
|
14
14
|
export declare function detectAgentCLIs(projectPath: string): AITool[];
|
|
15
|
+
/**
|
|
16
|
+
* Cowork/sandbox 환경의 마운트 경로 후보를 반환한다.
|
|
17
|
+
* /sessions/<id>/mnt/ 같은 경로에 실제 파일이 마운트됨.
|
|
18
|
+
*/
|
|
19
|
+
export declare function detectMountPaths(): string[];
|
|
20
|
+
/**
|
|
21
|
+
* 마운트 경로에서 에이전트 CLI 디렉토리를 감지한다.
|
|
22
|
+
*/
|
|
23
|
+
export declare function detectMountedCLIs(): {
|
|
24
|
+
tool: AITool;
|
|
25
|
+
basePath: string;
|
|
26
|
+
}[];
|
|
15
27
|
/**
|
|
16
28
|
* 홈 디렉토리에서 글로벌 에이전트 CLI 디렉토리를 감지한다.
|
|
17
29
|
* ~/{skillsDir}/ 가 존재하는 CLI를 반환.
|
|
@@ -32,3 +44,7 @@ export declare function scanLocalItems(projectPath: string, tool: AITool): Conte
|
|
|
32
44
|
* 글로벌 홈 디렉토리 소스의 개별 스킬/에이전트/커맨드/룰 항목을 반환한다.
|
|
33
45
|
*/
|
|
34
46
|
export declare function scanGlobalItems(tool: AITool, home?: string): ContentItem[];
|
|
47
|
+
/**
|
|
48
|
+
* 마운트 경로의 개별 스킬/에이전트/커맨드/룰 항목을 반환한다.
|
|
49
|
+
*/
|
|
50
|
+
export declare function scanMountedItems(basePath: string, tool: AITool): ContentItem[];
|
package/dist/lib/ai-tools.js
CHANGED
|
@@ -5,9 +5,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.AI_TOOLS = void 0;
|
|
7
7
|
exports.detectAgentCLIs = detectAgentCLIs;
|
|
8
|
+
exports.detectMountPaths = detectMountPaths;
|
|
9
|
+
exports.detectMountedCLIs = detectMountedCLIs;
|
|
8
10
|
exports.detectGlobalCLIs = detectGlobalCLIs;
|
|
9
11
|
exports.scanLocalItems = scanLocalItems;
|
|
10
12
|
exports.scanGlobalItems = scanGlobalItems;
|
|
13
|
+
exports.scanMountedItems = scanMountedItems;
|
|
11
14
|
const fs_1 = __importDefault(require("fs"));
|
|
12
15
|
const os_1 = __importDefault(require("os"));
|
|
13
16
|
const path_1 = __importDefault(require("path"));
|
|
@@ -47,6 +50,31 @@ exports.AI_TOOLS = [
|
|
|
47
50
|
function detectAgentCLIs(projectPath) {
|
|
48
51
|
return exports.AI_TOOLS.filter((tool) => fs_1.default.existsSync(path_1.default.join(projectPath, tool.skillsDir)));
|
|
49
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Cowork/sandbox 환경의 마운트 경로 후보를 반환한다.
|
|
55
|
+
* /sessions/<id>/mnt/ 같은 경로에 실제 파일이 마운트됨.
|
|
56
|
+
*/
|
|
57
|
+
function detectMountPaths() {
|
|
58
|
+
const home = os_1.default.homedir();
|
|
59
|
+
const mntPath = path_1.default.join(home, 'mnt');
|
|
60
|
+
if (!fs_1.default.existsSync(mntPath))
|
|
61
|
+
return [];
|
|
62
|
+
return [mntPath];
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 마운트 경로에서 에이전트 CLI 디렉토리를 감지한다.
|
|
66
|
+
*/
|
|
67
|
+
function detectMountedCLIs() {
|
|
68
|
+
const results = [];
|
|
69
|
+
for (const mnt of detectMountPaths()) {
|
|
70
|
+
for (const tool of exports.AI_TOOLS) {
|
|
71
|
+
if (fs_1.default.existsSync(path_1.default.join(mnt, tool.skillsDir))) {
|
|
72
|
+
results.push({ tool, basePath: mnt });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return results;
|
|
77
|
+
}
|
|
50
78
|
/**
|
|
51
79
|
* 홈 디렉토리에서 글로벌 에이전트 CLI 디렉토리를 감지한다.
|
|
52
80
|
* ~/{skillsDir}/ 가 존재하는 CLI를 반환.
|
|
@@ -100,3 +128,9 @@ function scanGlobalItems(tool, home) {
|
|
|
100
128
|
const basePath = path_1.default.join(home ?? os_1.default.homedir(), tool.skillsDir);
|
|
101
129
|
return scanItemsIn(basePath);
|
|
102
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* 마운트 경로의 개별 스킬/에이전트/커맨드/룰 항목을 반환한다.
|
|
133
|
+
*/
|
|
134
|
+
function scanMountedItems(basePath, tool) {
|
|
135
|
+
return scanItemsIn(path_1.default.join(basePath, tool.skillsDir));
|
|
136
|
+
}
|
package/dist/lib/guide.js
CHANGED
|
@@ -80,18 +80,17 @@ ${commands.map((cmd) => `- \`/${cmd.name}\`: ${cmd.description}`).join('\n')}
|
|
|
80
80
|
> ${config.description}
|
|
81
81
|
|
|
82
82
|
이 가이드는 Relay 에이전트를 설치하는 절차입니다.
|
|
83
|
-
각 Step의 명령어를 순서대로 실행하세요. 요약하지 말고 바로 Step
|
|
83
|
+
각 Step의 명령어를 순서대로 실행하세요. 요약하지 말고 바로 Step 0(환경 감지)부터 시작하세요.
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
Claude Code · Claude Cowork · Codex · Cursor · Antigravity
|
|
85
|
+
${index_js_1.SETUP_ENVIRONMENT}
|
|
87
86
|
|
|
88
87
|
${setupSection}
|
|
89
88
|
|
|
90
89
|
## Step ${installStepNum}. 에이전트 설치
|
|
91
90
|
|
|
92
|
-
|
|
93
|
-
relay install ${scopedSlug}
|
|
94
|
-
|
|
91
|
+
감지된 환경에 맞게 실행하세요:
|
|
92
|
+
- 환경 A (터미널): \`relay install ${scopedSlug}\`
|
|
93
|
+
- 환경 B (샌드박스): \`npx relayax-cli install ${scopedSlug}\`
|
|
95
94
|
|
|
96
95
|
설치 후 아래 의존성을 확인하고 환경을 구성합니다.
|
|
97
96
|
|
|
@@ -0,0 +1,267 @@
|
|
|
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.createMcpServer = createMcpServer;
|
|
7
|
+
exports.startMcpServer = startMcpServer;
|
|
8
|
+
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
9
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
10
|
+
const zod_1 = require("zod");
|
|
11
|
+
const config_js_1 = require("../lib/config.js");
|
|
12
|
+
const api_js_1 = require("../lib/api.js");
|
|
13
|
+
const slug_js_1 = require("../lib/slug.js");
|
|
14
|
+
const storage_js_1 = require("../lib/storage.js");
|
|
15
|
+
const ai_tools_js_1 = require("../lib/ai-tools.js");
|
|
16
|
+
const preamble_js_1 = require("../lib/preamble.js");
|
|
17
|
+
const installer_js_1 = require("../lib/installer.js");
|
|
18
|
+
const paths_js_1 = require("../lib/paths.js");
|
|
19
|
+
const fs_1 = __importDefault(require("fs"));
|
|
20
|
+
const path_1 = __importDefault(require("path"));
|
|
21
|
+
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
23
|
+
const pkg = require('../../package.json');
|
|
24
|
+
// ─── Helpers ───
|
|
25
|
+
async function resolveUsername(token) {
|
|
26
|
+
try {
|
|
27
|
+
const res = await fetch(`${config_js_1.API_URL}/api/auth/me`, {
|
|
28
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
29
|
+
});
|
|
30
|
+
if (!res.ok)
|
|
31
|
+
return undefined;
|
|
32
|
+
const body = await res.json();
|
|
33
|
+
return body.username;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function countFiles(dir) {
|
|
40
|
+
let count = 0;
|
|
41
|
+
if (!fs_1.default.existsSync(dir))
|
|
42
|
+
return 0;
|
|
43
|
+
for (const entry of fs_1.default.readdirSync(dir, { withFileTypes: true })) {
|
|
44
|
+
if (entry.isDirectory())
|
|
45
|
+
count += countFiles(path_1.default.join(dir, entry.name));
|
|
46
|
+
else
|
|
47
|
+
count++;
|
|
48
|
+
}
|
|
49
|
+
return count;
|
|
50
|
+
}
|
|
51
|
+
function jsonText(obj) {
|
|
52
|
+
return { type: 'text', text: JSON.stringify(obj) };
|
|
53
|
+
}
|
|
54
|
+
// ─── Server ───
|
|
55
|
+
function createMcpServer() {
|
|
56
|
+
const server = new mcp_js_1.McpServer({ name: 'relay', version: pkg.version }, { capabilities: { tools: {}, prompts: {} } });
|
|
57
|
+
// ═══ Tools ═══
|
|
58
|
+
server.tool('relay_search', '에이전트를 검색합니다', {
|
|
59
|
+
query: zod_1.z.string().describe('검색 키워드'),
|
|
60
|
+
tag: zod_1.z.string().optional().describe('태그 필터'),
|
|
61
|
+
}, async ({ query, tag }) => {
|
|
62
|
+
try {
|
|
63
|
+
const results = await (0, api_js_1.searchAgents)(query, tag);
|
|
64
|
+
return { content: [jsonText({ results: results.map((r) => ({ slug: r.slug, name: r.name, description: r.description, installs: r.install_count })) })] };
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
return { content: [jsonText({ error: String(err) })], isError: true };
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
server.tool('relay_install', '에이전트를 설치합니다', {
|
|
71
|
+
slug: zod_1.z.string().describe('에이전트 slug (예: @owner/name)'),
|
|
72
|
+
project_path: zod_1.z.string().optional().describe('프로젝트 경로 (기본: cwd)'),
|
|
73
|
+
}, async ({ slug: slugInput, project_path }) => {
|
|
74
|
+
try {
|
|
75
|
+
const projectPath = project_path ?? (0, paths_js_1.resolveProjectPath)();
|
|
76
|
+
const token = await (0, config_js_1.getValidToken)();
|
|
77
|
+
const parsed = await (0, slug_js_1.resolveSlug)(slugInput);
|
|
78
|
+
const fullSlug = parsed.full;
|
|
79
|
+
const agent = await (0, api_js_1.fetchAgentInfo)(fullSlug);
|
|
80
|
+
if (!agent)
|
|
81
|
+
throw new Error('에이전트 정보를 가져오지 못했습니다.');
|
|
82
|
+
if ((agent.visibility ?? 'public') !== 'public' && !token) {
|
|
83
|
+
return { content: [jsonText({ error: 'LOGIN_REQUIRED', message: '이 에이전트는 로그인이 필요합니다.' })], isError: true };
|
|
84
|
+
}
|
|
85
|
+
const tempDir = (0, storage_js_1.makeTempDir)();
|
|
86
|
+
try {
|
|
87
|
+
const tarPath = await (0, storage_js_1.downloadPackage)(agent.package_url, tempDir);
|
|
88
|
+
const agentDir = path_1.default.join(projectPath, '.relay', 'agents', parsed.owner, parsed.name);
|
|
89
|
+
if (fs_1.default.existsSync(agentDir))
|
|
90
|
+
fs_1.default.rmSync(agentDir, { recursive: true, force: true });
|
|
91
|
+
fs_1.default.mkdirSync(agentDir, { recursive: true });
|
|
92
|
+
await (0, storage_js_1.extractPackage)(tarPath, agentDir);
|
|
93
|
+
(0, preamble_js_1.injectPreambleToAgent)(agentDir, fullSlug);
|
|
94
|
+
const installed = (0, config_js_1.loadInstalled)();
|
|
95
|
+
installed[fullSlug] = { agent_id: agent.id, version: agent.version, installed_at: new Date().toISOString(), files: [agentDir] };
|
|
96
|
+
(0, config_js_1.saveInstalled)(installed);
|
|
97
|
+
await (0, api_js_1.reportInstall)(agent.id, fullSlug, agent.version);
|
|
98
|
+
(0, api_js_1.sendUsagePing)(agent.id, fullSlug, agent.version);
|
|
99
|
+
return { content: [jsonText({ status: 'ok', agent: agent.name, slug: fullSlug, version: agent.version, files: countFiles(agentDir), install_path: agentDir })] };
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
(0, storage_js_1.removeTempDir)(tempDir);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
return { content: [jsonText({ error: String(err) })], isError: true };
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
server.tool('relay_uninstall', '에이전트를 제거합니다', {
|
|
110
|
+
slug: zod_1.z.string().describe('에이전트 slug'),
|
|
111
|
+
}, async ({ slug: slugInput }) => {
|
|
112
|
+
try {
|
|
113
|
+
const local = (0, config_js_1.loadInstalled)();
|
|
114
|
+
const global = (0, config_js_1.loadGlobalInstalled)();
|
|
115
|
+
const entry = local[slugInput] ?? global[slugInput];
|
|
116
|
+
if (!entry) {
|
|
117
|
+
return { content: [jsonText({ error: 'NOT_INSTALLED', message: `'${slugInput}'는 설치되어 있지 않습니다.` })], isError: true };
|
|
118
|
+
}
|
|
119
|
+
let removed = 0;
|
|
120
|
+
if (local[slugInput]) {
|
|
121
|
+
removed += (0, installer_js_1.uninstallAgent)(local[slugInput].files).length;
|
|
122
|
+
delete local[slugInput];
|
|
123
|
+
(0, config_js_1.saveInstalled)(local);
|
|
124
|
+
}
|
|
125
|
+
if (global[slugInput]) {
|
|
126
|
+
removed += (0, installer_js_1.uninstallAgent)(global[slugInput].files).length;
|
|
127
|
+
delete global[slugInput];
|
|
128
|
+
(0, config_js_1.saveGlobalInstalled)(global);
|
|
129
|
+
}
|
|
130
|
+
return { content: [jsonText({ status: 'ok', removed_files: removed })] };
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
return { content: [jsonText({ error: String(err) })], isError: true };
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
server.tool('relay_list', '설치된 에이전트 목록을 조회합니다', {}, async () => {
|
|
137
|
+
const local = (0, config_js_1.loadInstalled)();
|
|
138
|
+
const global = (0, config_js_1.loadGlobalInstalled)();
|
|
139
|
+
const agents = [
|
|
140
|
+
...Object.entries(local).map(([slug, e]) => ({ slug, version: e.version, installed_at: e.installed_at, scope: 'local' })),
|
|
141
|
+
...Object.entries(global).map(([slug, e]) => ({ slug, version: e.version, installed_at: e.installed_at, scope: 'global' })),
|
|
142
|
+
];
|
|
143
|
+
return { content: [jsonText({ agents })] };
|
|
144
|
+
});
|
|
145
|
+
server.tool('relay_status', '현재 relay 환경 상태를 표시합니다', {
|
|
146
|
+
project_path: zod_1.z.string().optional().describe('프로젝트 경로'),
|
|
147
|
+
}, async ({ project_path }) => {
|
|
148
|
+
const projectPath = project_path ?? (0, paths_js_1.resolveProjectPath)();
|
|
149
|
+
const token = await (0, config_js_1.getValidToken)();
|
|
150
|
+
let username;
|
|
151
|
+
if (token)
|
|
152
|
+
username = await resolveUsername(token);
|
|
153
|
+
const detected = (0, ai_tools_js_1.detectAgentCLIs)(projectPath);
|
|
154
|
+
const mounted = (0, ai_tools_js_1.detectMountedCLIs)();
|
|
155
|
+
const relayYaml = path_1.default.join(projectPath, '.relay', 'relay.yaml');
|
|
156
|
+
let project = null;
|
|
157
|
+
if (fs_1.default.existsSync(relayYaml)) {
|
|
158
|
+
try {
|
|
159
|
+
const cfg = js_yaml_1.default.load(fs_1.default.readFileSync(relayYaml, 'utf-8'));
|
|
160
|
+
project = { is_agent: true, name: cfg.name, slug: cfg.slug, version: cfg.version };
|
|
161
|
+
}
|
|
162
|
+
catch { /* skip */ }
|
|
163
|
+
}
|
|
164
|
+
return { content: [jsonText({ login: { authenticated: !!token, username }, agent_clis: detected.map((t) => t.name), mounted_paths: mounted.map((m) => m.basePath), project })] };
|
|
165
|
+
});
|
|
166
|
+
server.tool('relay_scan', '배포 가능한 스킬/에이전트/커맨드를 스캔합니다', {
|
|
167
|
+
project_path: zod_1.z.string().optional().describe('프로젝트 경로'),
|
|
168
|
+
}, async ({ project_path }) => {
|
|
169
|
+
const projectPath = project_path ?? (0, paths_js_1.resolveProjectPath)();
|
|
170
|
+
const homeDir = (0, paths_js_1.resolveHome)();
|
|
171
|
+
const sources = [];
|
|
172
|
+
// 로컬
|
|
173
|
+
for (const tool of (0, ai_tools_js_1.detectAgentCLIs)(projectPath)) {
|
|
174
|
+
const items = (0, ai_tools_js_1.scanLocalItems)(projectPath, tool);
|
|
175
|
+
if (items.length > 0)
|
|
176
|
+
sources.push({ path: tool.skillsDir, location: 'local', name: tool.name, items: items.map((i) => ({ name: i.name, type: i.type })) });
|
|
177
|
+
}
|
|
178
|
+
// 글로벌
|
|
179
|
+
const { detectGlobalCLIs } = await import('../lib/ai-tools.js');
|
|
180
|
+
for (const tool of detectGlobalCLIs(homeDir)) {
|
|
181
|
+
const items = (0, ai_tools_js_1.scanGlobalItems)(tool, homeDir);
|
|
182
|
+
if (items.length > 0)
|
|
183
|
+
sources.push({ path: `~/${tool.skillsDir}`, location: 'global', name: `${tool.name} (global)`, items: items.map((i) => ({ name: i.name, type: i.type })) });
|
|
184
|
+
}
|
|
185
|
+
// 마운트 (Cowork)
|
|
186
|
+
for (const { tool, basePath } of (0, ai_tools_js_1.detectMountedCLIs)()) {
|
|
187
|
+
const items = (0, ai_tools_js_1.scanMountedItems)(basePath, tool);
|
|
188
|
+
if (items.length > 0)
|
|
189
|
+
sources.push({ path: `${basePath}/${tool.skillsDir}`, location: 'mounted', name: `${tool.name} (mounted)`, items: items.map((i) => ({ name: i.name, type: i.type })) });
|
|
190
|
+
}
|
|
191
|
+
return { content: [jsonText({ sources })] };
|
|
192
|
+
});
|
|
193
|
+
server.tool('relay_publish', '에이전트를 마켓플레이스에 배포합니다', {
|
|
194
|
+
project_path: zod_1.z.string().optional().describe('프로젝트 경로'),
|
|
195
|
+
}, async ({ project_path }) => {
|
|
196
|
+
const projectPath = project_path ?? (0, paths_js_1.resolveProjectPath)();
|
|
197
|
+
const relayYaml = path_1.default.join(projectPath, '.relay', 'relay.yaml');
|
|
198
|
+
if (!fs_1.default.existsSync(relayYaml)) {
|
|
199
|
+
return { content: [jsonText({ error: 'NOT_INITIALIZED', message: '.relay/relay.yaml이 없습니다. relay-publish 프롬프트를 사용하여 프로젝트를 먼저 설정하세요.' })], isError: true };
|
|
200
|
+
}
|
|
201
|
+
const token = await (0, config_js_1.getValidToken)();
|
|
202
|
+
if (!token) {
|
|
203
|
+
return { content: [jsonText({ error: 'LOGIN_REQUIRED', message: '배포하려면 로그인이 필요합니다.' })], isError: true };
|
|
204
|
+
}
|
|
205
|
+
const cfg = js_yaml_1.default.load(fs_1.default.readFileSync(relayYaml, 'utf-8'));
|
|
206
|
+
return { content: [jsonText({ status: 'ready', project_path: projectPath, name: cfg.name, slug: cfg.slug, version: cfg.version, message: '배포 준비 완료. relay-publish 프롬프트로 전체 워크플로우를 실행하세요.' })] };
|
|
207
|
+
});
|
|
208
|
+
// ═══ Prompts ═══
|
|
209
|
+
server.prompt('relay-install', '에이전트 검색 → 설치 가이드 워크플로우', {}, () => ({
|
|
210
|
+
messages: [{
|
|
211
|
+
role: 'user',
|
|
212
|
+
content: { type: 'text', text: `다음 가이드를 따라 에이전트를 설치하세요. relay MCP tool을 사용하여 각 단계를 실행합니다.
|
|
213
|
+
|
|
214
|
+
사용 가능한 tool: relay_status, relay_search, relay_install, relay_list, relay_status
|
|
215
|
+
|
|
216
|
+
## 워크플로우
|
|
217
|
+
|
|
218
|
+
1. relay_status로 로그인 상태 확인. 미인증이면 사용자에게 터미널에서 \`npx relayax-cli login --device\` 실행을 안내.
|
|
219
|
+
2. 사용자에게 어떤 에이전트를 찾는지 물어보기.
|
|
220
|
+
3. relay_search로 검색.
|
|
221
|
+
4. 검색 결과를 보여주고 선택하게 하기.
|
|
222
|
+
5. relay_install로 설치.
|
|
223
|
+
6. 설치 결과와 사용법 안내.` },
|
|
224
|
+
}],
|
|
225
|
+
}));
|
|
226
|
+
server.prompt('relay-publish', '에이전트 배포 가이드 워크플로우', {}, () => ({
|
|
227
|
+
messages: [{
|
|
228
|
+
role: 'user',
|
|
229
|
+
content: { type: 'text', text: `다음 가이드를 따라 에이전트를 배포하세요. relay MCP tool을 사용합니다.
|
|
230
|
+
|
|
231
|
+
사용 가능한 tool: relay_status, relay_scan, relay_status, relay_publish
|
|
232
|
+
|
|
233
|
+
## 워크플로우
|
|
234
|
+
|
|
235
|
+
1. relay_status로 로그인 상태 확인. 미인증이면 터미널에서 \`npx relayax-cli login --device\` 안내.
|
|
236
|
+
2. relay_scan으로 배포 가능한 스킬/에이전트/커맨드 목록 스캔.
|
|
237
|
+
3. 스캔 결과를 보여주고 사용자에게 어떤 항목을 배포할지 선택하게 하기.
|
|
238
|
+
4. relay_status로 프로젝트 상태 확인. .relay/relay.yaml이 없으면 프로젝트 생성 안내.
|
|
239
|
+
5. relay_publish로 배포.
|
|
240
|
+
6. 배포 결과를 사용자에게 보여주기.` },
|
|
241
|
+
}],
|
|
242
|
+
}));
|
|
243
|
+
server.prompt('relay-status', '환경 상태 확인', {}, () => ({
|
|
244
|
+
messages: [{
|
|
245
|
+
role: 'user',
|
|
246
|
+
content: { type: 'text', text: 'relay_status, relay_status, relay_list, relay_scan tool을 사용하여 현재 relay 환경 상태를 확인하고 사용자에게 보여주세요. 로그인 상태, 감지된 AI 도구, 프로젝트 정보, 설치된 에이전트, 배포 가능한 스킬 목록을 포함합니다.' },
|
|
247
|
+
}],
|
|
248
|
+
}));
|
|
249
|
+
server.prompt('relay-uninstall', '에이전트 제거 가이드', {}, () => ({
|
|
250
|
+
messages: [{
|
|
251
|
+
role: 'user',
|
|
252
|
+
content: { type: 'text', text: `다음 워크플로우를 따라 에이전트를 제거하세요:
|
|
253
|
+
|
|
254
|
+
1. relay_list로 설치된 에이전트 목록 표시.
|
|
255
|
+
2. 사용자에게 제거할 에이전트 선택하게 하기.
|
|
256
|
+
3. relay_uninstall로 제거.
|
|
257
|
+
4. 결과 보여주기.` },
|
|
258
|
+
}],
|
|
259
|
+
}));
|
|
260
|
+
return server;
|
|
261
|
+
}
|
|
262
|
+
// ─── Start ───
|
|
263
|
+
async function startMcpServer() {
|
|
264
|
+
const server = createMcpServer();
|
|
265
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
266
|
+
await server.connect(transport);
|
|
267
|
+
}
|
|
@@ -1,27 +1,17 @@
|
|
|
1
|
-
## Step
|
|
1
|
+
## Step 1. Relay CLI 준비
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
아래 명령어로 확인하세요:
|
|
3
|
+
### 환경 A (터미널)
|
|
5
4
|
|
|
6
5
|
```bash
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
`token-missing`이면, 이 세션에서 `relay login --device`로 새로 로그인해야 합니다.
|
|
11
|
-
이미 로그인한 토큰이 있다면 환경변수로 전달할 수도 있습니다:
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
export RELAY_TOKEN=<토큰>
|
|
6
|
+
npm install -g relayax-cli
|
|
7
|
+
relay init
|
|
15
8
|
```
|
|
16
9
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
## Step 1. Relay CLI 설치
|
|
20
|
-
|
|
21
|
-
relay CLI가 설치되어 있지 않다면:
|
|
10
|
+
이미 설치되어 있다면 최신 버전으로 업데이트합니다:
|
|
22
11
|
|
|
23
12
|
```bash
|
|
24
|
-
npm
|
|
13
|
+
npm update -g relayax-cli
|
|
14
|
+
relay init
|
|
25
15
|
```
|
|
26
16
|
|
|
27
17
|
Node.js가 없는 경우:
|
|
@@ -29,16 +19,12 @@ Node.js가 없는 경우:
|
|
|
29
19
|
- Windows: `winget install OpenJS.NodeJS.LTS`
|
|
30
20
|
- Linux: `curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash && source ~/.bashrc && nvm install --lts`
|
|
31
21
|
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
mkdir -p ~/.npm-global && npm config set prefix '~/.npm-global'
|
|
35
|
-
echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.zshrc && source ~/.zshrc
|
|
36
|
-
npm install -g relayax-cli
|
|
37
|
-
```
|
|
22
|
+
### 환경 B (샌드박스)
|
|
38
23
|
|
|
39
|
-
|
|
24
|
+
설치 없이 `npx`로 실행합니다. 이후 모든 `relay` 명령어를 `npx relayax-cli`로 대체하세요.
|
|
40
25
|
|
|
41
26
|
```bash
|
|
42
|
-
|
|
43
|
-
relay init
|
|
27
|
+
npx relayax-cli --version
|
|
44
28
|
```
|
|
29
|
+
|
|
30
|
+
정상 출력되면 준비 완료입니다.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
## Step 0. 환경 감지
|
|
2
|
+
|
|
3
|
+
이 가이드는 여러 환경에서 실행될 수 있습니다. 아래 환경 감지를 먼저 수행하고, 이후 모든 Step에서 감지된 환경에 맞는 방법을 사용하세요.
|
|
4
|
+
|
|
5
|
+
### 환경 판별
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
which relay 2>/dev/null && echo "cli-ok" || echo "cli-missing"
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
- `cli-ok` → **환경 A (터미널 + CLI 설치됨)** — 바로 Step 1로
|
|
12
|
+
- `cli-missing` → 아래 확인:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install -g relayax-cli 2>/dev/null && echo "install-ok" || echo "sandbox"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
- `install-ok` → **환경 A (터미널)** (`relay init` 실행 후 Step 1로)
|
|
19
|
+
- `sandbox` → **환경 B (샌드박스)** — `npx relayax-cli`로 실행
|
|
20
|
+
|
|
21
|
+
### 환경별 실행 방법
|
|
22
|
+
|
|
23
|
+
이후 가이드의 모든 `relay <명령어>`는 감지된 환경에 따라 실행합니다:
|
|
24
|
+
|
|
25
|
+
| 가이드 지시 | A. 터미널 | B. 샌드박스 |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| "relay status" | `relay status` | `npx relayax-cli status` |
|
|
28
|
+
| "relay install X" | `relay install X` | `npx relayax-cli install X` |
|
|
29
|
+
| "relay publish" | `relay publish` | `npx relayax-cli publish` |
|
|
30
|
+
| "relay login" | `relay login` | `npx relayax-cli login --device` |
|
|
31
|
+
|
|
32
|
+
처음 판별한 환경을 이후 계속 사용합니다.
|
|
@@ -1,35 +1,32 @@
|
|
|
1
1
|
## Step 2. 로그인
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**중요: 로그인은 반드시 터미널(bash)에서 CLI 명령어로 수행합니다. 브라우저에서 직접 relayax.com에 접속하거나 웹 로그인 페이지를 여는 것은 올바른 방법이 아닙니다.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
먼저 로그인 상태를 확인합니다. 이미 로그인되어 있으면 이 단계를 건너뛰세요.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- 환경 A: `relay status`
|
|
8
|
+
- 환경 B: `npx relayax-cli status`
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
로그인이 필요하면 **반드시 아래 bash 명령어를 실행**하세요.
|
|
11
|
+
|
|
12
|
+
### 환경 A (터미널)
|
|
12
13
|
|
|
13
14
|
```bash
|
|
14
15
|
relay login
|
|
15
16
|
```
|
|
16
17
|
|
|
17
18
|
- 브라우저가 자동으로 열리면 GitHub 또는 카카오 계정으로 로그인합니다.
|
|
18
|
-
- 브라우저가 열리지 않으면
|
|
19
|
+
- 브라우저가 열리지 않으면 출력된 URL을 별도로 엽니다: `open <URL>` (macOS) / `xdg-open <URL>` (Linux)
|
|
19
20
|
|
|
20
21
|
"✓ 로그인 완료"가 출력되면 다음 단계로 진행합니다.
|
|
21
22
|
|
|
22
|
-
###
|
|
23
|
-
|
|
24
|
-
Cowork, 원격 서버 등 브라우저 콜백이 localhost에 도달할 수 없는 환경에서는 이 방법을 사용합니다.
|
|
23
|
+
### 환경 B (샌드박스)
|
|
25
24
|
|
|
26
25
|
```bash
|
|
27
|
-
|
|
26
|
+
npx relayax-cli login --device
|
|
28
27
|
```
|
|
29
28
|
|
|
30
29
|
- 화면에 URL과 8자리 코드가 표시됩니다.
|
|
31
|
-
-
|
|
32
|
-
- 브라우저에서 코드를 입력하고 로그인을 승인합니다.
|
|
30
|
+
- 사용자에게 URL과 코드를 보여주세요. 사용자가 직접 브라우저에서 코드를 입력해야 합니다.
|
|
33
31
|
- CLI가 자동으로 승인을 감지하고 "✓ 로그인 완료"를 출력합니다.
|
|
34
|
-
|
|
35
|
-
`relay whoami`로 로그인 성공을 확인한 후 다음 단계로 진행합니다.
|
|
32
|
+
- timeout이 짧으면 `--timeout 300`을 추가하세요.
|
package/dist/prompts/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export declare const REQUIREMENTS_CHECK: string;
|
|
2
2
|
export declare const ERROR_HANDLING_GUIDE: string;
|
|
3
|
+
export declare const SETUP_ENVIRONMENT: string;
|
|
3
4
|
export declare const SETUP_CLI: string;
|
|
4
5
|
export declare const SETUP_LOGIN: string;
|
|
5
6
|
export declare const INSTALL_PROMPT: string;
|
package/dist/prompts/index.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.PUBLISH_PROMPT = exports.INSTALL_PROMPT = exports.SETUP_LOGIN = exports.SETUP_CLI = exports.ERROR_HANDLING_GUIDE = exports.REQUIREMENTS_CHECK = void 0;
|
|
6
|
+
exports.PUBLISH_PROMPT = exports.INSTALL_PROMPT = exports.SETUP_LOGIN = exports.SETUP_CLI = exports.SETUP_ENVIRONMENT = exports.ERROR_HANDLING_GUIDE = exports.REQUIREMENTS_CHECK = void 0;
|
|
7
7
|
const fs_1 = __importDefault(require("fs"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
function readPrompt(filename) {
|
|
@@ -15,6 +15,7 @@ function interpolate(template, vars) {
|
|
|
15
15
|
// ─── 공유 조각 ───
|
|
16
16
|
exports.REQUIREMENTS_CHECK = readPrompt('_requirements-check.md');
|
|
17
17
|
exports.ERROR_HANDLING_GUIDE = readPrompt('_error-handling.md');
|
|
18
|
+
exports.SETUP_ENVIRONMENT = readPrompt('_setup-environment.md');
|
|
18
19
|
exports.SETUP_CLI = readPrompt('_setup-cli.md');
|
|
19
20
|
exports.SETUP_LOGIN = readPrompt('_setup-login.md');
|
|
20
21
|
const fragments = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "relayax-cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.49",
|
|
4
4
|
"description": "RelayAX Agent Team Marketplace CLI - Install and manage agent teams",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"homepage": "https://relayax.com",
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@inquirer/prompts": "^8.3.2",
|
|
37
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
37
38
|
"commander": "^13.1.0",
|
|
38
39
|
"form-data": "^4.0.5",
|
|
39
40
|
"js-yaml": "^4.1.1",
|